mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-11-24 10:17:02 +00:00
Compare commits
1 Commits
remove-deb
...
handle-sup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbb4eab381 |
2
.github/workflows/update_frontend.yml
vendored
2
.github/workflows/update_frontend.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
rm -f supervisor/api/panel/home_assistant_frontend_supervisor-*.tar.gz
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
|
||||
branch: autoupdate-frontend
|
||||
|
||||
13
build.yaml
13
build.yaml
@@ -1,10 +1,13 @@
|
||||
image: ghcr.io/home-assistant/{arch}-hassio-supervisor
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.22-2025.11.1
|
||||
armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.22-2025.11.1
|
||||
armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.22-2025.11.1
|
||||
amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.22-2025.11.1
|
||||
i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.22-2025.11.1
|
||||
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.22
|
||||
armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.22
|
||||
armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.22
|
||||
amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.22
|
||||
i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.22
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
cosign:
|
||||
base_identity: https://github.com/home-assistant/docker-base/.*
|
||||
identity: https://github.com/home-assistant/supervisor/.*
|
||||
|
||||
@@ -4,7 +4,7 @@ aiohttp==3.13.2
|
||||
atomicwrites-homeassistant==1.4.1
|
||||
attrs==25.4.0
|
||||
awesomeversion==25.8.0
|
||||
backports.zstd==1.1.0
|
||||
backports.zstd==1.0.0
|
||||
blockbuster==1.5.25
|
||||
brotli==1.2.0
|
||||
ciso8601==2.3.3
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
astroid==4.0.2
|
||||
coverage==7.12.0
|
||||
mypy==1.18.2
|
||||
pre-commit==4.5.0
|
||||
pre-commit==4.4.0
|
||||
pylint==4.0.3
|
||||
pytest-aiohttp==1.1.0
|
||||
pytest-asyncio==1.3.0
|
||||
pytest-cov==7.0.0
|
||||
pytest-timeout==2.4.0
|
||||
pytest==9.0.1
|
||||
ruff==0.14.6
|
||||
time-machine==3.1.0
|
||||
ruff==0.14.5
|
||||
time-machine==3.0.0
|
||||
types-docker==7.1.0.20251009
|
||||
types-pyyaml==6.0.12.20250915
|
||||
types-requests==2.32.4.20250913
|
||||
|
||||
@@ -306,8 +306,6 @@ class DeviceType(IntEnum):
|
||||
VLAN = 11
|
||||
TUN = 16
|
||||
VETH = 20
|
||||
WIREGUARD = 29
|
||||
LOOPBACK = 32
|
||||
|
||||
|
||||
class WirelessMethodType(IntEnum):
|
||||
|
||||
@@ -134,10 +134,9 @@ class NetworkManager(DBusInterfaceProxy):
|
||||
async def check_connectivity(self, *, force: bool = False) -> ConnectivityState:
|
||||
"""Check the connectivity of the host."""
|
||||
if force:
|
||||
return ConnectivityState(
|
||||
await self.connected_dbus.call("check_connectivity")
|
||||
)
|
||||
return ConnectivityState(await self.connected_dbus.get("connectivity"))
|
||||
return await self.connected_dbus.call("check_connectivity")
|
||||
else:
|
||||
return await self.connected_dbus.get("connectivity")
|
||||
|
||||
async def connect(self, bus: MessageBus) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
|
||||
@@ -69,7 +69,7 @@ class NetworkConnection(DBusInterfaceProxy):
|
||||
@dbus_property
|
||||
def state(self) -> ConnectionStateType:
|
||||
"""Return the state of the connection."""
|
||||
return ConnectionStateType(self.properties[DBUS_ATTR_STATE])
|
||||
return self.properties[DBUS_ATTR_STATE]
|
||||
|
||||
@property
|
||||
def state_flags(self) -> set[ConnectionStateFlags]:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""NetworkInterface object for Network Manager."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from dbus_fast.aio.message_bus import MessageBus
|
||||
@@ -24,8 +23,6 @@ from .connection import NetworkConnection
|
||||
from .setting import NetworkSetting
|
||||
from .wireless import NetworkWireless
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkInterface(DBusInterfaceProxy):
|
||||
"""NetworkInterface object represents Network Manager Device objects.
|
||||
@@ -60,15 +57,7 @@ class NetworkInterface(DBusInterfaceProxy):
|
||||
@dbus_property
|
||||
def type(self) -> DeviceType:
|
||||
"""Return interface type."""
|
||||
try:
|
||||
return DeviceType(self.properties[DBUS_ATTR_DEVICE_TYPE])
|
||||
except ValueError:
|
||||
_LOGGER.debug(
|
||||
"Unknown device type %s for %s, treating as UNKNOWN",
|
||||
self.properties[DBUS_ATTR_DEVICE_TYPE],
|
||||
self.object_path,
|
||||
)
|
||||
return DeviceType.UNKNOWN
|
||||
return self.properties[DBUS_ATTR_DEVICE_TYPE]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
|
||||
@@ -34,7 +34,6 @@ class JobCondition(StrEnum):
|
||||
PLUGINS_UPDATED = "plugins_updated"
|
||||
RUNNING = "running"
|
||||
SUPERVISOR_UPDATED = "supervisor_updated"
|
||||
ARCHITECTURE_SUPPORTED = "architecture_supported"
|
||||
|
||||
|
||||
class JobConcurrency(StrEnum):
|
||||
|
||||
@@ -441,14 +441,6 @@ class Job(CoreSysAttributes):
|
||||
raise JobConditionException(
|
||||
f"'{method_name}' blocked from execution, supervisor needs to be updated first"
|
||||
)
|
||||
if (
|
||||
JobCondition.ARCHITECTURE_SUPPORTED in used_conditions
|
||||
and UnsupportedReason.SYSTEM_ARCHITECTURE
|
||||
in coresys.sys_resolution.unsupported
|
||||
):
|
||||
raise JobConditionException(
|
||||
f"'{method_name}' blocked from execution, unsupported system architecture"
|
||||
)
|
||||
|
||||
if JobCondition.PLUGINS_UPDATED in used_conditions and (
|
||||
out_of_date := [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""A collection of tasks."""
|
||||
|
||||
from contextlib import suppress
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import cast
|
||||
@@ -13,6 +14,7 @@ from ..exceptions import (
|
||||
BackupFileNotFoundError,
|
||||
HomeAssistantError,
|
||||
ObserverError,
|
||||
SupervisorUpdateError,
|
||||
)
|
||||
from ..homeassistant.const import LANDINGPAGE, WSType
|
||||
from ..jobs.const import JobConcurrency
|
||||
@@ -161,7 +163,6 @@ class Tasks(CoreSysAttributes):
|
||||
JobCondition.INTERNET_HOST,
|
||||
JobCondition.OS_SUPPORTED,
|
||||
JobCondition.RUNNING,
|
||||
JobCondition.ARCHITECTURE_SUPPORTED,
|
||||
],
|
||||
concurrency=JobConcurrency.REJECT,
|
||||
)
|
||||
@@ -174,7 +175,11 @@ class Tasks(CoreSysAttributes):
|
||||
"Found new Supervisor version %s, updating",
|
||||
self.sys_supervisor.latest_version,
|
||||
)
|
||||
await self.sys_supervisor.update()
|
||||
|
||||
# Errors are logged by the exceptions, we can't really do something
|
||||
# if an update fails here.
|
||||
with suppress(SupervisorUpdateError):
|
||||
await self.sys_supervisor.update()
|
||||
|
||||
async def _watchdog_homeassistant_api(self):
|
||||
"""Create scheduler task for monitoring running state of API.
|
||||
|
||||
@@ -23,5 +23,4 @@ PLUGIN_UPDATE_CONDITIONS = [
|
||||
JobCondition.HEALTHY,
|
||||
JobCondition.INTERNET_HOST,
|
||||
JobCondition.SUPERVISOR_UPDATED,
|
||||
JobCondition.ARCHITECTURE_SUPPORTED,
|
||||
]
|
||||
|
||||
@@ -58,7 +58,6 @@ class UnsupportedReason(StrEnum):
|
||||
SYSTEMD_JOURNAL = "systemd_journal"
|
||||
SYSTEMD_RESOLVED = "systemd_resolved"
|
||||
VIRTUALIZATION_IMAGE = "virtualization_image"
|
||||
SYSTEM_ARCHITECTURE = "system_architecture"
|
||||
|
||||
|
||||
class UnhealthyReason(StrEnum):
|
||||
|
||||
@@ -5,6 +5,8 @@ from ...coresys import CoreSys
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
SUPPORTED_OS = ["Debian GNU/Linux 12 (bookworm)"]
|
||||
|
||||
|
||||
def setup(coresys: CoreSys) -> EvaluateBase:
|
||||
"""Initialize evaluation-setup function."""
|
||||
@@ -31,4 +33,6 @@ class EvaluateOperatingSystem(EvaluateBase):
|
||||
|
||||
async def evaluate(self) -> bool:
|
||||
"""Run evaluation."""
|
||||
return not self.sys_os.available
|
||||
if self.sys_os.available:
|
||||
return False
|
||||
return self.sys_host.info.operating_system not in SUPPORTED_OS
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
"""Evaluation class for system architecture support."""
|
||||
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
|
||||
def setup(coresys: CoreSys) -> EvaluateBase:
|
||||
"""Initialize evaluation-setup function."""
|
||||
return EvaluateSystemArchitecture(coresys)
|
||||
|
||||
|
||||
class EvaluateSystemArchitecture(EvaluateBase):
|
||||
"""Evaluate if the current Supervisor architecture is supported."""
|
||||
|
||||
@property
|
||||
def reason(self) -> UnsupportedReason:
|
||||
"""Return a UnsupportedReason enum."""
|
||||
return UnsupportedReason.SYSTEM_ARCHITECTURE
|
||||
|
||||
@property
|
||||
def on_failure(self) -> str:
|
||||
"""Return a string that is printed when self.evaluate is True."""
|
||||
return "System architecture is no longer supported. Move to a supported system architecture."
|
||||
|
||||
@property
|
||||
def states(self) -> list[CoreState]:
|
||||
"""Return a list of valid states when this evaluation can run."""
|
||||
return [CoreState.INITIALIZE]
|
||||
|
||||
async def evaluate(self):
|
||||
"""Run evaluation."""
|
||||
return self.sys_host.info.sys_arch.supervisor in {
|
||||
"i386",
|
||||
"armhf",
|
||||
"armv7",
|
||||
}
|
||||
@@ -242,10 +242,9 @@ class Updater(FileConfiguration, CoreSysAttributes):
|
||||
@Job(
|
||||
name="updater_fetch_data",
|
||||
conditions=[
|
||||
JobCondition.ARCHITECTURE_SUPPORTED,
|
||||
JobCondition.INTERNET_SYSTEM,
|
||||
JobCondition.HOME_ASSISTANT_CORE_SUPPORTED,
|
||||
JobCondition.OS_SUPPORTED,
|
||||
JobCondition.HOME_ASSISTANT_CORE_SUPPORTED,
|
||||
],
|
||||
on_condition=UpdaterJobError,
|
||||
throttle_period=timedelta(seconds=30),
|
||||
|
||||
@@ -184,20 +184,3 @@ async def test_interface_becomes_unmanaged(
|
||||
assert wireless.is_connected is False
|
||||
assert eth0.connection is None
|
||||
assert connection.is_connected is False
|
||||
|
||||
|
||||
async def test_unknown_device_type(
|
||||
device_eth0_service: DeviceService, dbus_session_bus: MessageBus
|
||||
):
|
||||
"""Test unknown device types are handled gracefully."""
|
||||
interface = NetworkInterface("/org/freedesktop/NetworkManager/Devices/1")
|
||||
await interface.connect(dbus_session_bus)
|
||||
|
||||
# Emit an unknown device type (e.g., 1000 which doesn't exist in the enum)
|
||||
device_eth0_service.emit_properties_changed({"DeviceType": 1000})
|
||||
await device_eth0_service.ping()
|
||||
|
||||
# Should return UNKNOWN instead of crashing
|
||||
assert interface.type == DeviceType.UNKNOWN
|
||||
# Wireless should be None since it's not a wireless device
|
||||
assert interface.wireless is None
|
||||
|
||||
@@ -5,7 +5,10 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.evaluations.operating_system import EvaluateOperatingSystem
|
||||
from supervisor.resolution.evaluations.operating_system import (
|
||||
SUPPORTED_OS,
|
||||
EvaluateOperatingSystem,
|
||||
)
|
||||
|
||||
|
||||
async def test_evaluation(coresys: CoreSys):
|
||||
@@ -26,6 +29,12 @@ async def test_evaluation(coresys: CoreSys):
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
coresys.os._available = False
|
||||
|
||||
coresys.host._info = MagicMock(
|
||||
operating_system=SUPPORTED_OS[0], timezone=None, timezone_tzinfo=None
|
||||
)
|
||||
await operating_system()
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
"""Test that the evaluation ran as expected."""
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"""Test evaluation supported system architectures."""
|
||||
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.evaluations.system_architecture import (
|
||||
EvaluateSystemArchitecture,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arch", ["i386", "armhf", "armv7"])
|
||||
async def test_evaluation_unsupported_architectures(
|
||||
coresys: CoreSys,
|
||||
arch: str,
|
||||
):
|
||||
"""Test evaluation of unsupported system architectures."""
|
||||
system_architecture = EvaluateSystemArchitecture(coresys)
|
||||
await coresys.core.set_state(CoreState.INITIALIZE)
|
||||
|
||||
with patch.object(
|
||||
type(coresys.supervisor), "arch", PropertyMock(return_value=arch)
|
||||
):
|
||||
await system_architecture()
|
||||
assert system_architecture.reason in coresys.resolution.unsupported
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arch", ["amd64", "aarch64"])
|
||||
async def test_evaluation_supported_architectures(
|
||||
coresys: CoreSys,
|
||||
arch: str,
|
||||
):
|
||||
"""Test evaluation of supported system architectures."""
|
||||
system_architecture = EvaluateSystemArchitecture(coresys)
|
||||
await coresys.core.set_state(CoreState.INITIALIZE)
|
||||
|
||||
with patch.object(
|
||||
type(coresys.supervisor), "arch", PropertyMock(return_value=arch)
|
||||
):
|
||||
await system_architecture()
|
||||
assert system_architecture.reason not in coresys.resolution.unsupported
|
||||
Reference in New Issue
Block a user