diff --git a/supervisor/addons/__init__.py b/supervisor/addons/__init__.py index bebb78f38..53ae136cf 100644 --- a/supervisor/addons/__init__.py +++ b/supervisor/addons/__init__.py @@ -359,11 +359,17 @@ class AddonManager(CoreSysAttributes): """Sync add-ons DNS names.""" # Update hosts for addon in self.installed: - if not await addon.instance.is_running(): - continue - self.sys_plugins.dns.add_host( - ipv4=addon.ip_address, names=[addon.hostname], write=False - ) + try: + if not await addon.instance.is_running(): + continue + except DockerAPIError as err: + _LOGGER.warning("Add-on %s is corrupt: %s", addon.slug, err) + self.sys_core.healthy = False + self.sys_capture_exception(err) + else: + self.sys_plugins.dns.add_host( + ipv4=addon.ip_address, names=[addon.hostname], write=False + ) # Write hosts files with suppress(CoreDNSError): diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index c0a520d07..eb3c69aab 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -558,7 +558,7 @@ class Addon(AddonModel): await self.instance.run() except DockerAPIError as err: self.state = AddonState.ERROR - raise AddonsError(err) from None + raise AddonsError(err) from err else: self.state = AddonState.STARTED diff --git a/supervisor/const.py b/supervisor/const.py index bc705ed1a..c52f836b6 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -3,7 +3,7 @@ from enum import Enum from ipaddress import ip_network from pathlib import Path -SUPERVISOR_VERSION = "239" +SUPERVISOR_VERSION = "240" URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" URL_HASSIO_APPARMOR = "https://version.home-assistant.io/apparmor.txt" @@ -240,6 +240,7 @@ ATTR_SNAPSHOT_EXCLUDE = "snapshot_exclude" ATTR_SNAPSHOTS = "snapshots" ATTR_SOURCE = "source" ATTR_SQUASH = "squash" +ATTR_SSID = "ssid" ATTR_SSL = "ssl" ATTR_STAGE = "stage" ATTR_STARTUP = "startup" diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 4d3754215..881034f49 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -19,6 +19,8 @@ DBUS_OBJECT_HOSTNAME = "/org/freedesktop/hostname1" DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager" DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1" +DBUS_ATTR_802_WIRELESS = "802-11-wireless" +DBUS_ATTR_802_WIRELESS_SECURITY = "802-11-wireless-security" DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections" DBUS_ATTR_ADDRESS_DATA = "AddressData" DBUS_ATTR_BOOT_SLOT = "BootSlot" diff --git a/supervisor/dbus/network/configuration.py b/supervisor/dbus/network/configuration.py index 15f72be9b..5aafc391b 100644 --- a/supervisor/dbus/network/configuration.py +++ b/supervisor/dbus/network/configuration.py @@ -60,3 +60,11 @@ class NetworkDevice: ip4_address: int = attr.ib() device_type: int = attr.ib() real: bool = attr.ib() + + +@attr.s +class WirelessProperties: + """WirelessProperties object for Network Manager.""" + + properties: dict = attr.ib() + security: dict = attr.ib() diff --git a/supervisor/dbus/network/connection.py b/supervisor/dbus/network/connection.py index 7a67d420f..71aa78f21 100644 --- a/supervisor/dbus/network/connection.py +++ b/supervisor/dbus/network/connection.py @@ -4,6 +4,8 @@ from typing import Optional from ...const import ATTR_ADDRESS, ATTR_IPV4, ATTR_METHOD, ATTR_PREFIX from ...utils.gdbus import DBus from ..const import ( + DBUS_ATTR_802_WIRELESS, + DBUS_ATTR_802_WIRELESS_SECURITY, DBUS_ATTR_ADDRESS_DATA, DBUS_ATTR_CONNECTION, DBUS_ATTR_DEFAULT, @@ -23,6 +25,7 @@ from ..const import ( DBUS_NAME_IP4CONFIG, DBUS_NAME_NM, DBUS_OBJECT_BASE, + ConnectionType, ) from .configuration import ( AddressData, @@ -30,6 +33,7 @@ from .configuration import ( NetworkAttributes, NetworkDevice, NetworkSettings, + WirelessProperties, ) @@ -44,6 +48,7 @@ class NetworkConnection(NetworkAttributes): self._settings: Optional[NetworkSettings] = None self._ip4_config: Optional[IpConfiguration] = None self._device: Optional[NetworkDevice] + self._wireless: Optional[WirelessProperties] = None self.primary: bool = False @property @@ -81,6 +86,13 @@ class NetworkConnection(NetworkAttributes): """Return the uuid of the connection.""" return self._properties[DBUS_ATTR_UUID] + @property + def wireless(self) -> str: + """Return wireless properties if any.""" + if self.type != ConnectionType.WIRELESS: + return None + return self._wireless + @property def state(self) -> int: """ @@ -119,6 +131,11 @@ class NetworkConnection(NetworkAttributes): ), ) + self._wireless = WirelessProperties( + data.get(DBUS_ATTR_802_WIRELESS, {}), + data.get(DBUS_ATTR_802_WIRELESS_SECURITY, {}), + ) + self._device = NetworkDevice( device, device_data.get(DBUS_ATTR_DEVICE_INTERFACE), diff --git a/supervisor/dbus/network/interface.py b/supervisor/dbus/network/interface.py index 502720dcd..5803df246 100644 --- a/supervisor/dbus/network/interface.py +++ b/supervisor/dbus/network/interface.py @@ -4,6 +4,7 @@ from ..const import ( DBUS_NAME_CONNECTION_ACTIVE, DBUS_NAME_NM, DBUS_OBJECT_BASE, + ConnectionType, InterfaceMethod, ) from ..payloads.generate import interface_update_payload @@ -49,7 +50,7 @@ class NetworkInterface: return self.connection.ip4_config.address_data.prefix @property - def type(self) -> str: + def type(self) -> ConnectionType: """Return the interface type.""" return self.connection.type diff --git a/supervisor/dbus/payloads/generate.py b/supervisor/dbus/payloads/generate.py index f36a53851..523c1e95b 100644 --- a/supervisor/dbus/payloads/generate.py +++ b/supervisor/dbus/payloads/generate.py @@ -3,8 +3,8 @@ from pathlib import Path import jinja2 -from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_METHOD, ATTR_PREFIX -from ..const import InterfaceMethod +from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_METHOD, ATTR_PREFIX, ATTR_SSID +from ..const import ConnectionType, InterfaceMethod from ..network.utils import ip2int INTERFACE_UPDATE_TEMPLATE: Path = ( @@ -31,4 +31,15 @@ def interface_update_payload(interface, **kwargs) -> str: kwargs[ATTR_ADDRESS] = kwargs[ATTR_ADDRESS].split("/")[0] kwargs[ATTR_METHOD] = InterfaceMethod.MANUAL + if interface.type == ConnectionType.WIRELESS: + kwargs[ATTR_SSID] = ", ".join( + [ + f"0x{x}" + for x in interface.connection.wireless.properties[ATTR_SSID] + .encode() + .hex(",") + .split(",") + ] + ) + return template.render(interface=interface, options=kwargs) diff --git a/supervisor/dbus/payloads/interface_update.tmpl b/supervisor/dbus/payloads/interface_update.tmpl index a03b51256..927b893d0 100644 --- a/supervisor/dbus/payloads/interface_update.tmpl +++ b/supervisor/dbus/payloads/interface_update.tmpl @@ -23,4 +23,18 @@ 'gateway': <'{{ options.get("gateway", interface.gateway) }}'> } {% endif %} +{% if interface.type == "802-11-wireless" %} + , + '802-11-wireless': + { + 'security': <'802-11-wireless-security'>, + 'ssid': <[byte {{ options.ssid }}]> + + }, + '802-11-wireless-security': + { + 'auth-alg': <'{{ interface.connection.wireless.security['auth-alg'] }}'>, + 'key-mgmt': <'{{ interface.connection.wireless.security['key-mgmt'] }}'> + } +{% endif %} } \ No newline at end of file diff --git a/supervisor/docker/__init__.py b/supervisor/docker/__init__.py index ec47efb26..fdfc0decf 100644 --- a/supervisor/docker/__init__.py +++ b/supervisor/docker/__init__.py @@ -149,7 +149,7 @@ class DockerAPI: container.start() except (docker.errors.DockerException, requests.RequestException) as err: _LOGGER.error("Can't start %s: %s", name, err) - raise DockerAPIError(err) from None + raise DockerAPIError(err) from err # Update metadata with suppress(docker.errors.DockerException, requests.RequestException): diff --git a/supervisor/misc/tasks.py b/supervisor/misc/tasks.py index 66bb9b747..3838c590b 100644 --- a/supervisor/misc/tasks.py +++ b/supervisor/misc/tasks.py @@ -308,7 +308,7 @@ class Tasks(CoreSysAttributes): try: await addon.start() except AddonsError as err: - _LOGGER.error("Watchdog %s reanimation failed!", addon.slug) + _LOGGER.error("Watchdog %s reanimation failed with %s", addon.slug, err) self.sys_capture_exception(err) async def _watchdog_addon_application(self): @@ -338,7 +338,7 @@ class Tasks(CoreSysAttributes): try: await addon.restart() except AddonsError as err: - _LOGGER.error("Watchdog %s reanimation failed!", addon.slug) + _LOGGER.error("Watchdog %s reanimation failed with %s", addon.slug, err) self.sys_capture_exception(err) finally: self._cache[addon.slug] = 0 diff --git a/supervisor/store/data.py b/supervisor/store/data.py index c41f5a431..a7082d30c 100644 --- a/supervisor/store/data.py +++ b/supervisor/store/data.py @@ -74,7 +74,21 @@ class StoreData(CoreSysAttributes): def _read_addons_folder(self, path, repository): """Read data from add-ons folder.""" - for addon in path.glob("**/config.json"): + try: + addon_list = path.glob("**/config.json") + except OSError as err: + self.sys_core.healthy = False + _LOGGER.critical( + "Can't process %s because of Filesystem issues: %s", repository, err + ) + self.sys_capture_exception(err) + return + + for addon in addon_list: + # Ingore git artefacts + if ".git" in addon.parts: + continue + try: addon_config = read_json_file(addon) except JsonFileError: diff --git a/tests/dbus/payloads/test_interface_update_payload.py b/tests/dbus/payloads/test_interface_update_payload.py index f6b496c8f..536064235 100644 --- a/tests/dbus/payloads/test_interface_update_payload.py +++ b/tests/dbus/payloads/test_interface_update_payload.py @@ -1,12 +1,13 @@ """Test interface update payload.""" import pytest +from supervisor.dbus.const import ConnectionType from supervisor.dbus.payloads.generate import interface_update_payload from supervisor.utils.gdbus import DBus @pytest.mark.asyncio -async def test_interface_update_payload(network_interface): +async def test_interface_update_payload_ethernet(network_interface): """Test interface update payload.""" data = interface_update_payload(network_interface, **{"method": "auto"}) assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" @@ -17,3 +18,18 @@ async def test_interface_update_payload(network_interface): assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual" assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1" assert DBus.parse_gvariant(data)["ipv4"]["dns"] == [16843009, 16777217] + + +@pytest.mark.asyncio +async def test_interface_update_payload_wireless(network_interface): + """Test interface update payload.""" + network_interface.connection._properties["Type"] = ConnectionType.WIRELESS + data = interface_update_payload(network_interface, **{"method": "auto"}) + assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" + + data = interface_update_payload( + network_interface, **{"address": "1.1.1.1", "dns": ["1.1.1.1", "1.0.0.1"]} + ) + assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual" + assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1" + assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == "NETT" diff --git a/tests/fixtures/org_freedesktop_NetworkManager.fixture b/tests/fixtures/org_freedesktop_NetworkManager.fixture index 9cb8ee938..233343596 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager.fixture +++ b/tests/fixtures/org_freedesktop_NetworkManager.fixture @@ -1 +1 @@ -({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}},) \ No newline at end of file +({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},) \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1.fixture index 9cb8ee938..233343596 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1.fixture +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1.fixture @@ -1 +1 @@ -({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}},) \ No newline at end of file +({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},) \ No newline at end of file