diff --git a/supervisor/const.py b/supervisor/const.py index 2dd722c5c..ab8f0ccfa 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -202,6 +202,7 @@ ATTR_HASSIO_API = "hassio_api" ATTR_HASSIO_ROLE = "hassio_role" ATTR_HASSOS = "hassos" ATTR_HASSOS_UNRESTRICTED = "hassos_unrestricted" +ATTR_HASSOS_UPGRADE = "hassos_upgrade" ATTR_HEALTHY = "healthy" ATTR_HEARTBEAT_LED = "heartbeat_led" ATTR_HOMEASSISTANT = "homeassistant" diff --git a/supervisor/updater.py b/supervisor/updater.py index 2b004c8a2..32932a7df 100644 --- a/supervisor/updater.py +++ b/supervisor/updater.py @@ -17,8 +17,8 @@ from .const import ( ATTR_CHANNEL, ATTR_CLI, ATTR_DNS, - ATTR_HASSOS, ATTR_HASSOS_UNRESTRICTED, + ATTR_HASSOS_UPGRADE, ATTR_HOMEASSISTANT, ATTR_IMAGE, ATTR_MULTICAST, @@ -93,13 +93,46 @@ class Updater(FileConfiguration, CoreSysAttributes): @property def version_hassos(self) -> AwesomeVersion | None: """Return latest version of HassOS.""" - return self._data.get(ATTR_HASSOS) + upgrade_map = self.upgrade_map_hassos + unrestricted = self.version_hassos_unrestricted + + # If no upgrade map exists, fall back to unrestricted version + if not upgrade_map: + return unrestricted + + # If we have no unrestricted version or no current OS version, return unrestricted + if ( + not unrestricted + or not self.sys_os.version + or self.sys_os.version.major is None + ): + return unrestricted + + current_major = str(self.sys_os.version.major) + # Check if there's an upgrade path for current major version + if current_major in upgrade_map: + last_in_major = AwesomeVersion(upgrade_map[current_major]) + # If we're not at the last version in our major, upgrade to that first + if self.sys_os.version != last_in_major: + return last_in_major + # If we are at the last version in our major, check for next major + next_major = str(int(self.sys_os.version.major) + 1) + if next_major in upgrade_map: + return AwesomeVersion(upgrade_map[next_major]) + + # Fall back to unrestricted version + return unrestricted @property def version_hassos_unrestricted(self) -> AwesomeVersion | None: """Return latest version of HassOS ignoring upgrade restrictions.""" return self._data.get(ATTR_HASSOS_UNRESTRICTED) + @property + def upgrade_map_hassos(self) -> dict[str, str] | None: + """Return HassOS upgrade map.""" + return self._data.get(ATTR_HASSOS_UPGRADE) + @property def version_cli(self) -> AwesomeVersion | None: """Return latest version of CLI.""" @@ -291,18 +324,10 @@ class Updater(FileConfiguration, CoreSysAttributes): if self.sys_os.board: self._data[ATTR_OTA] = data["ota"] if version := data["hassos"].get(self.sys_os.board): - self._data[ATTR_HASSOS_UNRESTRICTED] = version + self._data[ATTR_HASSOS_UNRESTRICTED] = AwesomeVersion(version) + # Store the upgrade map for persistent access + self._data[ATTR_HASSOS_UPGRADE] = data.get("hassos-upgrade", {}) events.append("os") - upgrade_map = data.get("hassos-upgrade", {}) - if last_in_major := upgrade_map.get(str(self.sys_os.version.major)): - if self.sys_os.version != AwesomeVersion(last_in_major): - version = last_in_major - elif last_in_next_major := upgrade_map.get( - str(int(self.sys_os.version.major) + 1) - ): - version = last_in_next_major - - self._data[ATTR_HASSOS] = AwesomeVersion(version) else: _LOGGER.warning( "Board '%s' not found in version file. No OS updates.", diff --git a/supervisor/validate.py b/supervisor/validate.py index e435ab074..8a20ff9b0 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -24,6 +24,7 @@ from .const import ( ATTR_FORCE_SECURITY, ATTR_HASSOS, ATTR_HASSOS_UNRESTRICTED, + ATTR_HASSOS_UPGRADE, ATTR_HOMEASSISTANT, ATTR_ID, ATTR_IMAGE, @@ -129,6 +130,9 @@ SCHEMA_UPDATER_CONFIG = vol.Schema( vol.Optional(ATTR_SUPERVISOR): version_tag, vol.Optional(ATTR_HASSOS): version_tag, vol.Optional(ATTR_HASSOS_UNRESTRICTED): version_tag, + vol.Optional(ATTR_HASSOS_UPGRADE): vol.Schema( + {vol.Extra: version_tag}, extra=vol.ALLOW_EXTRA + ), vol.Optional(ATTR_CLI): version_tag, vol.Optional(ATTR_DNS): version_tag, vol.Optional(ATTR_AUDIO): version_tag, diff --git a/tests/api/test_root.py b/tests/api/test_root.py index 9d233ace0..3cc93e3f2 100644 --- a/tests/api/test_root.py +++ b/tests/api/test_root.py @@ -48,7 +48,7 @@ async def test_api_available_updates( "version_latest": "9.2.1", } - coresys.updater._data["hassos"] = "321" + coresys.updater._data["hassos_unrestricted"] = "321" coresys.os._version = "123" updates = await available_updates() assert len(updates) == 2