Files
computer-playbook/roles/web-app-homeassistant/README.md
T
kevinveenbirkenbachandClaude Opus 5 ebcfbce773 fix(homeassistant): enforce the read-only tool flag
mutating_tools_enabled: false was a claim with nothing behind it. The agent include list is built by subtracting tools.mutating from tools.allowlist, and the role named no mutating tool, so the subtraction removed nothing and every client was offered HassTurnOn, HassTurnOff, HassBroadcast, HassCancelAllTimers and the three list writers while the declaration said mutations were off.

Naming the seven turns the flag into the control it claims to be: a client now sees GetDateTime, GetLiveContext and todo_get_items, and setting the flag to true restores the full surface. The adapter already refuses a contract whose tool carries no mutating verdict; this brings the one native provider that serves actuating tools under the same rule. Entity exposure still bounds what the read tools reach.

The README described entity exposure as the only control and said the write tools stay listed; it now describes both layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 16:22:54 +02:00

8.7 KiB

Home Assistant

Description

Home Assistant is an open-source home automation platform that connects smart-home devices from many vendors behind a single local interface. It keeps device state, dashboards, and automation rules on the machine that runs it, and exposes them through a web interface, a REST API, and a built-in MCP server.

Overview

This role deploys Home Assistant as a container on its own canonical domain behind the reverse proxy, with the configuration directory kept in a persistent volume. It renders a configuration.yaml that trusts the proxy through use_x_forwarded_for and the project-wide trusted-proxy CIDRs, and it attaches the hub to a shared overlay network. With Hermes Agent deployed alongside it, the role declares the hub as the MCP server of the deployment: an internal /api/mcp endpoint plus the long-lived access token that MCP clients present.

Cosmos

The diagram places Home Assistant in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).

flowchart LR
    subgraph deps [Dependencies]
        dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
        dep_svc_net_tor["svc-net-tor 🐳🐝"]
        dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
        dep_web_app_flowise["web-app-flowise 🐳🐝"]
        dep_web_app_hermes["web-app-hermes 🐳🐝"]
        dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
        dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
        dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
        dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
    end
    subgraph role [web-app-homeassistant 🐳🐝]
        svc_homeassistant["homeassistant"]
        svc_sso["sso ❌"]
        svc_logout["logout ❌"]
        svc_dashboard["dashboard"]
        svc_prometheus["prometheus"]
        svc_tor["tor"]
        svc_container_backup["container_backup"]
        svc_openwebui["openwebui"]
        svc_hermes["hermes"]
        svc_openclaw["openclaw"]
        svc_flowise["flowise"]
    end
    dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
    dep_svc_net_tor -. "0..1" .-> svc_tor
    dep_web_app_dashboard -. "0..1" .-> svc_dashboard
    dep_web_app_flowise -. "0..1" .-> svc_flowise
    dep_web_app_hermes -. "0..1" .-> svc_hermes
    dep_web_app_keycloak -- "0..0" --> svc_sso
    dep_web_app_openclaw -. "0..1" .-> svc_openclaw
    dep_web_app_openwebui -. "0..1" .-> svc_openwebui
    dep_web_app_prometheus -. "0..1" .-> svc_prometheus
    linkStyle 5 stroke:red;

Solid 1:1 edges are fixed relationships; dashed 0..1 edges are conditional (enabled only in matching deployments); red 0..0 edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in meta/main.yml.

Features

  • Containerized hub: Runs the official Home Assistant image on both the Docker Compose and Swarm stacks behind the reverse proxy.
  • Reverse-proxy trust: Renders a configuration.yaml with default_config, use_x_forwarded_for, and the project-wide trusted-proxy CIDRs listed in trusted_proxies. In swarm the hub joins the proxy's own overlay, so the forwarded request arrives from that network rather than from the hub's, and trusting only the hub's subnet makes Home Assistant answer every proxied request with HTTP 400.
  • Persistent configuration: Mounts /config as a named volume and hands it to the container backup service when volume backups are part of the deployment.
  • MCP server contract: Declares the built-in MCP server at /api/mcp over streamable HTTP as an internal, bearer-token-authenticated endpoint, and generates the token that MCP clients present. Adding the MCP Server integration itself stays a Home Assistant onboarding step.
  • Token verified against the hub: Every deploy asks the hub whether the stored token still authenticates and re-mints it when the hub rejects it, then fails the deploy if the fresh token is rejected too. A hub whose .storage/auth was recreated leaves a stored token pointing at a deleted refresh token, and clients would receive a 401 on every call.
  • Two-layer tool policy: Home Assistant offers no per-tool filter of its own; enabling the Assist API always publishes its actuating intents (HassTurnOn, HassTurnOff, the todo-list writers). The role names those in tools.mutating, and mutating_tools_enabled: false withholds them from every client's include list, so an agent is offered the read tools only. Entity exposure bounds the rest: the role exposes no entity to Assist, and making the hub actuate anything is an explicit operator step per entity.
  • Guest MCP probe: A Playwright spec asserts that an unauthenticated request to the MCP endpoint is never answered with a 2xx.
  • Monitoring and dashboard entries: Registers the hub with the metrics and dashboard services when those are part of the deployment.

Quick Setup

Development

Clone, set up the workstation, and deploy Home Assistant onto the local stack:

git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-homeassistant full_cycle=false

Production

Run the published image to provision the inventory and deploy Home Assistant to a managed server (the mounted volume persists the inventory):

APP=web-app-homeassistant
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"

docker run --rm -it \
  -v "$PWD/inventories:/etc/infinito.nexus/inventories" \
  -e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
  ghcr.io/infinito-nexus/core/debian bash -c '
    INVENTORY=/etc/infinito.nexus/inventories/production
    infinito administration inventory provision "$INVENTORY" \
      --inventory-file "$INVENTORY/devices.yml" \
      --host "$HOST" \
      --include "$APP" \
      --vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
    infinito administration deploy dedicated "$INVENTORY/devices.yml" \
      --password-file "$INVENTORY/.password" \
      --diff -vv'

MCP Server

Home Assistant ships the MCP server as a native integration. The role enables it through mcp.enabled, which stays true only while an MCP client role is part of the deployment.

Endpoint

Property Value
Transport Streamable HTTP
URL http://homeassistant:8123/api/mcp
Exposure internal, container network only

Auth

Clients present a long-lived access token as Authorization: Bearer <token>. The token is minted once per deployment and kept in sys-token-store under the administrator user, not in the role's credentials: block. An unauthenticated request is answered with 404: Home Assistant does not reveal the endpoint.

Authorization subject

auth_subject: administrator: Home Assistant scopes the token to the account that minted it. This deployment mints it for the administrator, so a call arrives as the administrator whoever asked the client. Entity exposure still bounds the reach. Getting to the tool server is gated on the role's mcp RBAC group.

Tool categories

tools/list returns 10 tools: entity control (HassTurnOn, HassTurnOff), timers, broadcast, to-do list read and write, date and time, and GetLiveContext for the current state.

Default state

Off. mcp.enabled is false unless web-app-flowise, web-app-hermes, web-app-openclaw or web-app-openwebui is deployed alongside.

Tool scope

Two controls stack. The actuating tools are named in tools.mutating, and mutating_tools_enabled: false withholds them from every client's include list, so an agent is offered GetDateTime, GetLiveContext and todo_get_items only. Entity exposure bounds those: only entities exposed to Assist are reachable, and the role exposes none. Setting mutating_tools_enabled: true offers the actuating tools as well; entity exposure then decides what they can reach.

How to disable

Remove the MCP client roles from the deployment, or pin mcp.enabled: false for this role in the inventory. The integration is then not configured and the endpoint stops answering.

Credits

Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).