diff --git a/homecore_controller/CHANGELOG.md b/homecore_controller/CHANGELOG.md index c01491c..b72636a 100644 --- a/homecore_controller/CHANGELOG.md +++ b/homecore_controller/CHANGELOG.md @@ -1,5 +1,14 @@ # HomeCore Controller Changelog +## 0.1.13 + +- Fixes HomeCore self-update architecture for Home Assistant OS. +- Home Assistant Supervisor intentionally blocks an app from updating itself directly. +- HomeCore now asks Home Assistant Core to run the supported `update.install` action for the HomeCore Controller update entity. +- Keeps repository validation, backup-before-update, restart recovery and completion reporting. +- Automatic rollout remains disabled; deployment is still manual and per-controller from HomeCore Admin. +- No functional home-control changes from 0.1.12. + ## 0.1.12 - End-to-end validation release for HomeCore Admin manual per-controller updating using the Supervisor `admin` API role. diff --git a/homecore_controller/app/main.py b/homecore_controller/app/main.py index cfcb80b..8394711 100644 --- a/homecore_controller/app/main.py +++ b/homecore_controller/app/main.py @@ -16,7 +16,7 @@ from cryptography.hazmat.primitives import serialization import uvicorn from updater import start_update_manager -VERSION = "0.1.12" +VERSION = "0.1.13" print( "HOMECORE SUPERVISOR: token " diff --git a/homecore_controller/app/updater.py b/homecore_controller/app/updater.py index 08a0e58..4b61d6f 100644 --- a/homecore_controller/app/updater.py +++ b/homecore_controller/app/updater.py @@ -62,6 +62,74 @@ def supervisor_request(method, path, payload=None, timeout=90): return body +HOME_ASSISTANT_API = "http://supervisor/core/api" + + +def homeassistant_request(method, path, payload=None, timeout=90): + r = httpx.request( + method, + HOME_ASSISTANT_API + path, + headers=supervisor_headers(), + json=payload, + timeout=timeout, + ) + r.raise_for_status() + + try: + return r.json() + except Exception: + return {} + + +def find_homecore_update_entity(current_version, target_version): + states = homeassistant_request("GET", "/states", timeout=30) + if not isinstance(states, list): + raise RuntimeError("Home Assistant did not return entity states") + + candidates = [] + + for state in states: + entity_id = str(state.get("entity_id", "")) + if not entity_id.startswith("update."): + continue + + attrs = state.get("attributes") or {} + title = str(attrs.get("title") or "") + friendly = str(attrs.get("friendly_name") or "") + installed = str(attrs.get("installed_version") or "") + latest = str(attrs.get("latest_version") or "") + + name_match = ( + "homecore controller" in title.lower() + or "homecore controller" in friendly.lower() + ) + + version_match = ( + installed == current_version + and latest == target_version + ) + + if name_match: + candidates.append((state, version_match)) + + exact = [state for state, version_match in candidates if version_match] + if exact: + return exact[0] + + if len(candidates) == 1: + return candidates[0][0] + + if not candidates: + raise RuntimeError( + "HomeCore Controller update entity was not found in Home Assistant" + ) + + raise RuntimeError( + "Multiple HomeCore Controller update entities were found; " + "cannot choose safely" + ) + + def find_homecore_app(): data = supervisor_request("GET", "/store/addons") @@ -321,17 +389,44 @@ class HomeCoreUpdateManager: "Backup requested; Supervisor update starting", ) - supervisor_request( + update_entity = find_homecore_update_entity( + self.current_version, + target, + ) + update_entity_id = str(update_entity.get("entity_id", "")).strip() + + if not update_entity_id: + raise RuntimeError( + "HomeCore Controller update entity ID is missing" + ) + + print( + "HOMECORE UPDATE: Home Assistant update action " + f"entity={update_entity_id} target={target}", + flush=True, + ) + + # Home Assistant Supervisor deliberately blocks an app from calling + # its own /store/addons//update endpoint. Ask Home Assistant Core + # to execute the normal update.install action instead. Core performs + # the Supervisor update, exactly as if an administrator installed the + # app update from the Home Assistant UI. + # + # The state above is written before this call because updating this + # app stops the current container. On restart, mark_completed_if_required + # reports completion back to HomeCore Admin. + homeassistant_request( "POST", - f"/store/addons/{slug}/update", + "/services/update/install", { + "entity_id": update_entity_id, "backup": True, - "background": True, }, + timeout=300, ) print( - "HOMECORE UPDATE: Supervisor accepted manual update job", + "HOMECORE UPDATE: Home Assistant accepted manual update action", flush=True, ) diff --git a/homecore_controller/config.yaml b/homecore_controller/config.yaml index ce1ffdc..841f809 100644 --- a/homecore_controller/config.yaml +++ b/homecore_controller/config.yaml @@ -1,5 +1,5 @@ name: HomeCore Controller -version: "0.1.12" +version: "0.1.13" slug: homecore_controller description: HomeCore local controller agent for Home Assistant Green. url: https://homecore.home-core.co.uk