mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 22:56:31 +00:00
OS-Agent support (#2811)
* OS-Agent support * add agent to host feature * Add support for os-agent on devcontainer * Rename core * fix tests * add setter * add cgroup / apparmor * all interfaces added * fix import * Add tests * More tests * Finish tests * reformating xml files * fix doc string * address comments * change return value * fix tests * Update supervisor/dbus/agent/__init__.py Co-authored-by: Joakim Sørensen <joasoe@gmail.com> * Update scripts/supervisor.sh Co-authored-by: Joakim Sørensen <joasoe@gmail.com> Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
7c6c982414
commit
2b6829a786
@ -48,6 +48,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
jq \
|
||||
dbus \
|
||||
network-manager \
|
||||
apparmor-utils \
|
||||
libpulse0 \
|
||||
&& bash <(curl https://getvcn.codenotary.com -L) \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
@ -88,6 +88,21 @@ function init_udev() {
|
||||
udevadm trigger && udevadm settle
|
||||
}
|
||||
|
||||
function init_os-agent() {
|
||||
if pgrep os-agent; then
|
||||
echo "os-agent is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f /usr/sbin/os-agent ]; then
|
||||
curl -Lo /usr/sbin/os-agent https://github.com/home-assistant/os-agent/releases/latest/download/os-agent-debian-amd64.bin
|
||||
curl -Lo /etc/dbus-1/system.d/io.hass.conf https://raw.githubusercontent.com/home-assistant/os-agent/main/contrib/io.hass.conf
|
||||
chmod a+x /usr/sbin/os-agent
|
||||
fi
|
||||
|
||||
/usr/sbin/os-agent &
|
||||
}
|
||||
|
||||
echo "Run Supervisor"
|
||||
|
||||
start_docker
|
||||
@ -99,6 +114,7 @@ if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" ==
|
||||
docker rm -f hassio_supervisor
|
||||
init_dbus
|
||||
init_udev
|
||||
init_os-agent
|
||||
cleanup_lastboot
|
||||
run_supervisor
|
||||
stop_docker
|
||||
@ -111,6 +127,7 @@ else
|
||||
cleanup_docker
|
||||
init_dbus
|
||||
init_udev
|
||||
init_os-agent
|
||||
run_supervisor
|
||||
stop_docker
|
||||
fi
|
@ -33,7 +33,7 @@ from .const import (
|
||||
)
|
||||
from .core import Core
|
||||
from .coresys import CoreSys
|
||||
from .dbus import DBusManager
|
||||
from .dbus.manager import DBusManager
|
||||
from .discovery import Discovery
|
||||
from .hardware.module import HardwareManager
|
||||
from .hassos import HassOS
|
||||
|
@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
||||
from .arch import CpuArch
|
||||
from .auth import Auth
|
||||
from .core import Core
|
||||
from .dbus import DBusManager
|
||||
from .dbus.manager import DBusManager
|
||||
from .discovery import Discovery
|
||||
from .hardware.module import HardwareManager
|
||||
from .hassos import HassOS
|
||||
|
@ -1,84 +1 @@
|
||||
"""D-Bus interface objects."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from ..const import SOCKET_DBUS
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from .hostname import Hostname
|
||||
from .interface import DBusInterface
|
||||
from .logind import Logind
|
||||
from .network import NetworkManager
|
||||
from .rauc import Rauc
|
||||
from .systemd import Systemd
|
||||
from .timedate import TimeDate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DBusManager(CoreSysAttributes):
|
||||
"""A DBus Interface handler."""
|
||||
|
||||
def __init__(self, coresys: CoreSys) -> None:
|
||||
"""Initialize D-Bus interface."""
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
self._systemd: Systemd = Systemd()
|
||||
self._logind: Logind = Logind()
|
||||
self._hostname: Hostname = Hostname()
|
||||
self._rauc: Rauc = Rauc()
|
||||
self._network: NetworkManager = NetworkManager()
|
||||
self._timedate: TimeDate = TimeDate()
|
||||
|
||||
@property
|
||||
def systemd(self) -> Systemd:
|
||||
"""Return the systemd interface."""
|
||||
return self._systemd
|
||||
|
||||
@property
|
||||
def logind(self) -> Logind:
|
||||
"""Return the logind interface."""
|
||||
return self._logind
|
||||
|
||||
@property
|
||||
def timedate(self) -> TimeDate:
|
||||
"""Return the timedate interface."""
|
||||
return self._timedate
|
||||
|
||||
@property
|
||||
def hostname(self) -> Hostname:
|
||||
"""Return the hostname interface."""
|
||||
return self._hostname
|
||||
|
||||
@property
|
||||
def rauc(self) -> Rauc:
|
||||
"""Return the rauc interface."""
|
||||
return self._rauc
|
||||
|
||||
@property
|
||||
def network(self) -> NetworkManager:
|
||||
"""Return NetworkManager interface."""
|
||||
return self._network
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Connect interfaces to D-Bus."""
|
||||
if not SOCKET_DBUS.exists():
|
||||
_LOGGER.error(
|
||||
"No D-Bus support on Host. Disabled any kind of host control!"
|
||||
)
|
||||
return
|
||||
|
||||
dbus_loads: List[DBusInterface] = [
|
||||
self.systemd,
|
||||
self.logind,
|
||||
self.hostname,
|
||||
self.timedate,
|
||||
self.network,
|
||||
self.rauc,
|
||||
]
|
||||
for dbus in dbus_loads:
|
||||
try:
|
||||
await dbus.connect()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't load dbus interface %s: %s", dbus.name, err)
|
||||
|
||||
self.sys_host.supported_features.cache_clear()
|
||||
"""D-Bus interfaces."""
|
||||
|
99
supervisor/dbus/agent/__init__.py
Normal file
99
supervisor/dbus/agent/__init__.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""OS-Agent implementation for DBUS."""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from ...exceptions import DBusError, DBusInterfaceError
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_DIAGNOSTICS,
|
||||
DBUS_ATTR_VERSION,
|
||||
DBUS_NAME_HAOS,
|
||||
DBUS_OBJECT_HAOS,
|
||||
)
|
||||
from ..interface import DBusInterface, dbus_property
|
||||
from ..utils import dbus_connected
|
||||
from .apparmor import AppArmor
|
||||
from .cgroup import CGroup
|
||||
from .datadisk import DataDisk
|
||||
from .system import System
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OSAgent(DBusInterface):
|
||||
"""Handle D-Bus interface for OS-Agent."""
|
||||
|
||||
name = DBUS_NAME_HAOS
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize Properties."""
|
||||
self.properties: Dict[str, Any] = {}
|
||||
|
||||
self._cgroup: CGroup = CGroup()
|
||||
self._apparmor: AppArmor = AppArmor()
|
||||
self._system: System = System()
|
||||
self._datadisk: DataDisk = DataDisk()
|
||||
|
||||
@property
|
||||
def cgroup(self) -> CGroup:
|
||||
"""Return CGroup DBUS object."""
|
||||
return self._cgroup
|
||||
|
||||
@property
|
||||
def apparmor(self) -> AppArmor:
|
||||
"""Return AppArmor DBUS object."""
|
||||
return self._apparmor
|
||||
|
||||
@property
|
||||
def system(self) -> System:
|
||||
"""Return System DBUS object."""
|
||||
return self._system
|
||||
|
||||
@property
|
||||
def datadisk(self) -> DataDisk:
|
||||
"""Return DataDisk DBUS object."""
|
||||
return self._datadisk
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def version(self) -> AwesomeVersion:
|
||||
"""Return version of OS-Agent."""
|
||||
return AwesomeVersion(self.properties[DBUS_ATTR_VERSION])
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def diagnostics(self) -> bool:
|
||||
"""Return if diagnostics is enabled on OS-Agent."""
|
||||
return self.properties[DBUS_ATTR_DIAGNOSTICS]
|
||||
|
||||
@diagnostics.setter
|
||||
def diagnostics(self, value: bool) -> None:
|
||||
"""Enable or disable OS-Agent diagnostics."""
|
||||
asyncio.create_task(
|
||||
self.dbus.set_property(DBUS_NAME_HAOS, DBUS_ATTR_DIAGNOSTICS, value)
|
||||
)
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
try:
|
||||
self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS)
|
||||
await self.cgroup.connect()
|
||||
await self.apparmor.connect()
|
||||
await self.system.connect()
|
||||
await self.datadisk.connect()
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to OS-Agent")
|
||||
except DBusInterfaceError:
|
||||
_LOGGER.warning(
|
||||
"No OS-Agent support on the host. Some Host functions have been disabled."
|
||||
)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS)
|
||||
await self.apparmor.update()
|
||||
await self.datadisk.update()
|
48
supervisor/dbus/agent/apparmor.py
Normal file
48
supervisor/dbus/agent/apparmor.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""AppArmor object for OS-Agent."""
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_PARSER_VERSION,
|
||||
DBUS_NAME_HAOS,
|
||||
DBUS_NAME_HAOS_APPARMOR,
|
||||
DBUS_OBJECT_HAOS_APPARMOR,
|
||||
)
|
||||
from ..interface import DBusInterface, dbus_property
|
||||
from ..utils import dbus_connected
|
||||
|
||||
|
||||
class AppArmor(DBusInterface):
|
||||
"""AppArmor object for OS Agent."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize Properties."""
|
||||
self.properties: Dict[str, Any] = {}
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def version(self) -> AwesomeVersion:
|
||||
"""Return version of host AppArmor parser."""
|
||||
return AwesomeVersion(self.properties[DBUS_ATTR_PARSER_VERSION])
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_APPARMOR)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS_APPARMOR)
|
||||
|
||||
@dbus_connected
|
||||
async def load_profile(self, profile: Path, cache: Path) -> None:
|
||||
"""Load/Update AppArmor profile."""
|
||||
await self.dbus.AppArmor.LoadProfile(profile.as_posix(), cache.as_posix())
|
||||
|
||||
@dbus_connected
|
||||
async def unload_profile(self, profile: Path, cache: Path) -> None:
|
||||
"""Remove AppArmor profile."""
|
||||
await self.dbus.AppArmor.UnloadProfile(profile.as_posix(), cache.as_posix())
|
19
supervisor/dbus/agent/cgroup.py
Normal file
19
supervisor/dbus/agent/cgroup.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""CGroup object for OS-Agent."""
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP
|
||||
from ..interface import DBusInterface
|
||||
from ..utils import dbus_connected
|
||||
|
||||
|
||||
class CGroup(DBusInterface):
|
||||
"""CGroup object for OS Agent."""
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP)
|
||||
|
||||
@dbus_connected
|
||||
async def add_devices_allowed(self, container_id: str, permission: str) -> None:
|
||||
"""Update cgroup devices and add new devices."""
|
||||
await self.dbus.CGroup.AddDevicesAllowed(container_id, permission)
|
41
supervisor/dbus/agent/datadisk.py
Normal file
41
supervisor/dbus/agent/datadisk.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""DataDisk object for OS-Agent."""
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_CURRENT_DEVICE,
|
||||
DBUS_NAME_HAOS,
|
||||
DBUS_NAME_HAOS_DATADISK,
|
||||
DBUS_OBJECT_HAOS_DATADISK,
|
||||
)
|
||||
from ..interface import DBusInterface, dbus_property
|
||||
from ..utils import dbus_connected
|
||||
|
||||
|
||||
class DataDisk(DBusInterface):
|
||||
"""DataDisk object for OS Agent."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize Properties."""
|
||||
self.properties: Dict[str, Any] = {}
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def current_device(self) -> Path:
|
||||
"""Return current device used for data."""
|
||||
return Path(self.properties[DBUS_ATTR_CURRENT_DEVICE])
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_DATADISK)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS_DATADISK)
|
||||
|
||||
@dbus_connected
|
||||
async def change_device(self, device: Path) -> None:
|
||||
"""Load/Update AppArmor profile."""
|
||||
await self.dbus.DataDisk.ChangeDevice(device.as_posix())
|
19
supervisor/dbus/agent/system.py
Normal file
19
supervisor/dbus/agent/system.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""System object for OS-Agent."""
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM
|
||||
from ..interface import DBusInterface
|
||||
from ..utils import dbus_connected
|
||||
|
||||
|
||||
class System(DBusInterface):
|
||||
"""System object for OS Agent."""
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM)
|
||||
|
||||
@dbus_connected
|
||||
async def schedule_wipe_device(self) -> None:
|
||||
"""Schedule a factory reset on next system boot."""
|
||||
await self.dbus.System.ScheduleWipeDevice()
|
@ -20,6 +20,12 @@ DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED = (
|
||||
DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1"
|
||||
DBUS_NAME_LOGIND = "org.freedesktop.login1"
|
||||
DBUS_NAME_TIMEDATE = "org.freedesktop.timedate1"
|
||||
DBUS_NAME_HAOS = "io.hass.os"
|
||||
DBUS_NAME_HAOS_CGROUP = "io.hass.os.CGroup"
|
||||
DBUS_NAME_HAOS_APPARMOR = "io.hass.os.AppArmor"
|
||||
DBUS_NAME_HAOS_SYSTEM = "io.hass.os.System"
|
||||
DBUS_NAME_HAOS_DATADISK = "io.hass.os.DataDisk"
|
||||
|
||||
|
||||
DBUS_OBJECT_BASE = "/"
|
||||
DBUS_OBJECT_DNS = "/org/freedesktop/NetworkManager/DnsManager"
|
||||
@ -29,6 +35,11 @@ DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager"
|
||||
DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1"
|
||||
DBUS_OBJECT_LOGIND = "/org/freedesktop/login1"
|
||||
DBUS_OBJECT_TIMEDATE = "/org/freedesktop/timedate1"
|
||||
DBUS_OBJECT_HAOS = "/io/hass/os"
|
||||
DBUS_OBJECT_HAOS_CGROUP = "/io/hass/os/CGroup"
|
||||
DBUS_OBJECT_HAOS_APPARMOR = "/io/hass/os/AppArmor"
|
||||
DBUS_OBJECT_HAOS_SYSTEM = "/io/hass/os/System"
|
||||
DBUS_OBJECT_HAOS_DATADISK = "/io/hass/os/DataDisk"
|
||||
|
||||
DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections"
|
||||
DBUS_ATTR_ACTIVE_CONNECTION = "ActiveConnection"
|
||||
@ -77,6 +88,9 @@ DBUS_ATTR_LOCALRTC = "LocalRTC"
|
||||
DBUS_ATTR_NTP = "NTP"
|
||||
DBUS_ATTR_NTPSYNCHRONIZED = "NTPSynchronized"
|
||||
DBUS_ATTR_TIMEUSEC = "TimeUSec"
|
||||
DBUS_ATTR_DIAGNOSTICS = "Diagnostics"
|
||||
DBUS_ATTR_CURRENT_DEVICE = "CurrentDevice"
|
||||
DBUS_ATTR_PARSER_VERSION = "ParserVersion"
|
||||
|
||||
|
||||
class RaucState(str, Enum):
|
||||
|
93
supervisor/dbus/manager.py
Normal file
93
supervisor/dbus/manager.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""D-Bus interface objects."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from ..const import SOCKET_DBUS
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from .agent import OSAgent
|
||||
from .hostname import Hostname
|
||||
from .interface import DBusInterface
|
||||
from .logind import Logind
|
||||
from .network import NetworkManager
|
||||
from .rauc import Rauc
|
||||
from .systemd import Systemd
|
||||
from .timedate import TimeDate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DBusManager(CoreSysAttributes):
|
||||
"""A DBus Interface handler."""
|
||||
|
||||
def __init__(self, coresys: CoreSys) -> None:
|
||||
"""Initialize D-Bus interface."""
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
self._systemd: Systemd = Systemd()
|
||||
self._logind: Logind = Logind()
|
||||
self._hostname: Hostname = Hostname()
|
||||
self._rauc: Rauc = Rauc()
|
||||
self._network: NetworkManager = NetworkManager()
|
||||
self._agent: OSAgent = OSAgent()
|
||||
self._timedate: TimeDate = TimeDate()
|
||||
|
||||
@property
|
||||
def systemd(self) -> Systemd:
|
||||
"""Return the systemd interface."""
|
||||
return self._systemd
|
||||
|
||||
@property
|
||||
def logind(self) -> Logind:
|
||||
"""Return the logind interface."""
|
||||
return self._logind
|
||||
|
||||
@property
|
||||
def hostname(self) -> Hostname:
|
||||
"""Return the hostname interface."""
|
||||
return self._hostname
|
||||
|
||||
@property
|
||||
def rauc(self) -> Rauc:
|
||||
"""Return the rauc interface."""
|
||||
return self._rauc
|
||||
|
||||
@property
|
||||
def network(self) -> NetworkManager:
|
||||
"""Return NetworkManager interface."""
|
||||
return self._network
|
||||
|
||||
@property
|
||||
def agent(self) -> OSAgent:
|
||||
"""Return OS-Agent interface."""
|
||||
return self._agent
|
||||
|
||||
@property
|
||||
def timedate(self) -> TimeDate:
|
||||
"""Return the timedate interface."""
|
||||
return self._timedate
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Connect interfaces to D-Bus."""
|
||||
if not SOCKET_DBUS.exists():
|
||||
_LOGGER.error(
|
||||
"No D-Bus support on Host. Disabled any kind of host control!"
|
||||
)
|
||||
return
|
||||
|
||||
dbus_loads: List[DBusInterface] = [
|
||||
self.agent,
|
||||
self.systemd,
|
||||
self.logind,
|
||||
self.hostname,
|
||||
self.timedate,
|
||||
self.network,
|
||||
self.rauc,
|
||||
]
|
||||
for dbus in dbus_loads:
|
||||
_LOGGER.info("Load dbus interface %s", dbus.name)
|
||||
try:
|
||||
await dbus.connect()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't load dbus interface %s: %s", dbus.name, err)
|
||||
|
||||
self.sys_host.supported_features.cache_clear()
|
@ -4,10 +4,10 @@ from functools import lru_cache
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from ..const import HostFeature
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import HassioError, PulseAudioError
|
||||
from .apparmor import AppArmorControl
|
||||
from .const import HostFeature
|
||||
from .control import SystemControl
|
||||
from .info import InfoCenter
|
||||
from .network import NetworkManager
|
||||
@ -85,8 +85,11 @@ class HostManager(CoreSysAttributes):
|
||||
if self.sys_dbus.timedate.is_connected:
|
||||
features.append(HostFeature.TIMEDATE)
|
||||
|
||||
if self.sys_dbus.agent.is_connected:
|
||||
features.append(HostFeature.AGENT)
|
||||
|
||||
if self.sys_hassos.available:
|
||||
features.append(HostFeature.HASSOS)
|
||||
features.append(HostFeature.HAOS)
|
||||
|
||||
return features
|
||||
|
||||
@ -100,6 +103,9 @@ class HostManager(CoreSysAttributes):
|
||||
if self.sys_dbus.network.is_connected:
|
||||
await self.network.update()
|
||||
|
||||
if self.sys_dbus.agent.is_connected:
|
||||
await self.sys_dbus.agent.update()
|
||||
|
||||
with suppress(PulseAudioError):
|
||||
await self.sound.update()
|
||||
|
||||
|
@ -33,3 +33,16 @@ class WifiMode(str, Enum):
|
||||
MESH = "mesh"
|
||||
ADHOC = "adhoc"
|
||||
AP = "ap"
|
||||
|
||||
|
||||
class HostFeature(str, Enum):
|
||||
"""Host feature."""
|
||||
|
||||
HAOS = "haos"
|
||||
HOSTNAME = "hostname"
|
||||
NETWORK = "network"
|
||||
REBOOT = "reboot"
|
||||
SERVICES = "services"
|
||||
SHUTDOWN = "shutdown"
|
||||
AGENT = "agent"
|
||||
TIMEDATE = "timedate"
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""A collection of tasks."""
|
||||
import logging
|
||||
|
||||
from ..const import AddonState, HostFeature
|
||||
from ..const import AddonState
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
AddonsError,
|
||||
@ -12,6 +12,7 @@ from ..exceptions import (
|
||||
MulticastError,
|
||||
ObserverError,
|
||||
)
|
||||
from ..host.const import HostFeature
|
||||
from ..jobs.decorator import Job, JobCondition
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Evaluation class for network manager."""
|
||||
from typing import List
|
||||
|
||||
from ...const import CoreState, HostFeature
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ...host.const import HostFeature
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Evaluation class for systemd."""
|
||||
from typing import List
|
||||
|
||||
from ...const import CoreState, HostFeature
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ...host.const import HostFeature
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
|
@ -67,6 +67,7 @@ MONITOR: str = "gdbus monitor --system --dest {bus}"
|
||||
WAIT: str = "gdbus wait --system --activate {bus} --timeout 5 {bus}"
|
||||
|
||||
DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll"
|
||||
DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set"
|
||||
|
||||
|
||||
def _convert_bytes(value: str) -> str:
|
||||
@ -225,6 +226,16 @@ class DBus:
|
||||
_LOGGER.error("No attributes returned for %s", interface)
|
||||
raise DBusFatalError() from err
|
||||
|
||||
async def set_property(
|
||||
self, interface: str, name: str, value: Any
|
||||
) -> Dict[str, Any]:
|
||||
"""Set a property from interface."""
|
||||
try:
|
||||
return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0]
|
||||
except IndexError as err:
|
||||
_LOGGER.error("No Set attribute %s for %s", name, interface)
|
||||
raise DBusFatalError() from err
|
||||
|
||||
async def _send(self, command: List[str], silent=False) -> str:
|
||||
"""Send command over dbus."""
|
||||
# Run command
|
||||
|
1
tests/dbus/agent/__init__.py
Normal file
1
tests/dbus/agent/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for OS-Agent."""
|
15
tests/dbus/agent/test_agent.py
Normal file
15
tests/dbus/agent/test_agent.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""Test OSAgent dbus interface."""
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
|
||||
|
||||
async def test_dbus_osagent(coresys: CoreSys):
|
||||
"""Test coresys dbus connection."""
|
||||
assert coresys.dbus.agent.version is None
|
||||
assert coresys.dbus.agent.diagnostics is None
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
await coresys.dbus.agent.update()
|
||||
|
||||
assert coresys.dbus.agent.version == "1.1.0"
|
||||
assert coresys.dbus.agent.diagnostics
|
53
tests/dbus/agent/test_apparmor.py
Normal file
53
tests/dbus/agent/test_apparmor.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""Test AppArmor/Agent dbus interface."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import DBusNotConnectedError
|
||||
|
||||
|
||||
async def test_dbus_osagent_apparmor(coresys: CoreSys):
|
||||
"""Test coresys dbus connection."""
|
||||
assert coresys.dbus.agent.apparmor.version is None
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
await coresys.dbus.agent.update()
|
||||
|
||||
assert coresys.dbus.agent.apparmor.version == "2.13.2"
|
||||
|
||||
|
||||
async def test_dbus_osagent_apparmor_load(coresys: CoreSys):
|
||||
"""Load AppArmor Profile on host."""
|
||||
|
||||
with pytest.raises(DBusNotConnectedError):
|
||||
await coresys.dbus.agent.apparmor.load_profile(
|
||||
Path("/data/apparmor/profile"), Path("/data/apparmor/cache")
|
||||
)
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
|
||||
assert (
|
||||
await coresys.dbus.agent.apparmor.load_profile(
|
||||
Path("/data/apparmor/profile"), Path("/data/apparmor/cache")
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
async def test_dbus_osagent_apparmor_unload(coresys: CoreSys):
|
||||
"""Unload AppArmor Profile on host."""
|
||||
|
||||
with pytest.raises(DBusNotConnectedError):
|
||||
await coresys.dbus.agent.apparmor.unload_profile(
|
||||
Path("/data/apparmor/profile"), Path("/data/apparmor/cache")
|
||||
)
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
|
||||
assert (
|
||||
await coresys.dbus.agent.apparmor.unload_profile(
|
||||
Path("/data/apparmor/profile"), Path("/data/apparmor/cache")
|
||||
)
|
||||
is None
|
||||
)
|
20
tests/dbus/agent/test_cgroup.py
Normal file
20
tests/dbus/agent/test_cgroup.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""Test CGroup/Agent dbus interface."""
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import DBusNotConnectedError
|
||||
|
||||
|
||||
async def test_dbus_osagent_cgroup_add_devices(coresys: CoreSys):
|
||||
"""Test wipe data partition on host."""
|
||||
|
||||
with pytest.raises(DBusNotConnectedError):
|
||||
await coresys.dbus.agent.cgroup.add_devices_allowed("9324kl23j4kl", "*:* rwm")
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
|
||||
assert (
|
||||
await coresys.dbus.agent.cgroup.add_devices_allowed("9324kl23j4kl", "*:* rwm")
|
||||
is None
|
||||
)
|
28
tests/dbus/agent/test_datadisk.py
Normal file
28
tests/dbus/agent/test_datadisk.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Test Datadisk/Agent dbus interface."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import DBusNotConnectedError
|
||||
|
||||
|
||||
async def test_dbus_osagent_datadisk(coresys: CoreSys):
|
||||
"""Test coresys dbus connection."""
|
||||
assert coresys.dbus.agent.datadisk.current_device is None
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
await coresys.dbus.agent.update()
|
||||
|
||||
assert coresys.dbus.agent.datadisk.current_device.as_posix() == "/dev/sda"
|
||||
|
||||
|
||||
async def test_dbus_osagent_datadisk_change_device(coresys: CoreSys):
|
||||
"""Change datadisk on device."""
|
||||
|
||||
with pytest.raises(DBusNotConnectedError):
|
||||
await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb"))
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
|
||||
assert await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb")) is None
|
17
tests/dbus/agent/test_system.py
Normal file
17
tests/dbus/agent/test_system.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""Test System/Agent dbus interface."""
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import DBusNotConnectedError
|
||||
|
||||
|
||||
async def test_dbus_osagent_system_wipe(coresys: CoreSys):
|
||||
"""Test wipe data partition on host."""
|
||||
|
||||
with pytest.raises(DBusNotConnectedError):
|
||||
await coresys.dbus.agent.system.schedule_wipe_device()
|
||||
|
||||
await coresys.dbus.agent.connect()
|
||||
|
||||
assert await coresys.dbus.agent.system.schedule_wipe_device() is None
|
1
tests/dbus/network/__init__.py
Normal file
1
tests/dbus/network/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for DBUS NetworkManager."""
|
1
tests/fixtures/io_hass_os.json
vendored
Normal file
1
tests/fixtures/io_hass_os.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{ "Version": "1.1.0", "Diagnostics": true }
|
51
tests/fixtures/io_hass_os.xml
vendored
Normal file
51
tests/fixtures/io_hass_os.xml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/io/hass/os">
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="out" type="s" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="props" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="in">
|
||||
</arg>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface" type="s" direction="out">
|
||||
</arg>
|
||||
<arg name="changed_properties" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
<arg name="invalidates_properties" type="as" direction="out">
|
||||
</arg>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="io.hass.os">
|
||||
<property name="Version" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates">
|
||||
</annotation>
|
||||
</property>
|
||||
<property name="Diagnostics" type="b" access="readwrite">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true">
|
||||
</annotation>
|
||||
</property>
|
||||
</interface>
|
||||
</node>
|
1
tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture
vendored
Normal file
1
tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<true>,)
|
1
tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture
vendored
Normal file
1
tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<true>,)
|
1
tests/fixtures/io_hass_os_AppArmor.json
vendored
Normal file
1
tests/fixtures/io_hass_os_AppArmor.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{ "ParserVersion": "2.13.2" }
|
63
tests/fixtures/io_hass_os_AppArmor.xml
vendored
Normal file
63
tests/fixtures/io_hass_os_AppArmor.xml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/io/hass/os/AppArmor">
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="out" type="s" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="props" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="in">
|
||||
</arg>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface" type="s" direction="out">
|
||||
</arg>
|
||||
<arg name="changed_properties" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
<arg name="invalidates_properties" type="as" direction="out">
|
||||
</arg>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="io.hass.os.AppArmor">
|
||||
<method name="LoadProfile">
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="UnloadProfile">
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<property name="ParserVersion" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates">
|
||||
</annotation>
|
||||
</property>
|
||||
</interface>
|
||||
</node>
|
1
tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture
vendored
Normal file
1
tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<true>,)
|
51
tests/fixtures/io_hass_os_CGroup.xml
vendored
Normal file
51
tests/fixtures/io_hass_os_CGroup.xml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/io/hass/os/CGroup">
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="out" type="s" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="props" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="in">
|
||||
</arg>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface" type="s" direction="out">
|
||||
</arg>
|
||||
<arg name="changed_properties" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
<arg name="invalidates_properties" type="as" direction="out">
|
||||
</arg>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="io.hass.os.CGroup">
|
||||
<method name="AddDevicesAllowed">
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
1
tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture
vendored
Normal file
1
tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<true>,)
|
1
tests/fixtures/io_hass_os_DataDisk.json
vendored
Normal file
1
tests/fixtures/io_hass_os_DataDisk.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{ "CurrentDevice": "/dev/sda" }
|
53
tests/fixtures/io_hass_os_DataDisk.xml
vendored
Normal file
53
tests/fixtures/io_hass_os_DataDisk.xml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/io/hass/os/DataDisk">
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="out" type="s" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="props" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="in">
|
||||
</arg>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface" type="s" direction="out">
|
||||
</arg>
|
||||
<arg name="changed_properties" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
<arg name="invalidates_properties" type="as" direction="out">
|
||||
</arg>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="io.hass.os.DataDisk">
|
||||
<method name="ChangeDevice">
|
||||
<arg type="s" direction="in">
|
||||
</arg>
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<property name="CurrentDevice" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true">
|
||||
</annotation>
|
||||
</property>
|
||||
</interface>
|
||||
</node>
|
1
tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture
vendored
Normal file
1
tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<true>,)
|
51
tests/fixtures/io_hass_os_System.xml
vendored
Normal file
51
tests/fixtures/io_hass_os_System.xml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/io/hass/os/System">
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="out" type="s" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="props" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="property" type="s" direction="in">
|
||||
</arg>
|
||||
<arg name="value" type="v" direction="in">
|
||||
</arg>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface" type="s" direction="out">
|
||||
</arg>
|
||||
<arg name="changed_properties" type="a{sv}" direction="out">
|
||||
</arg>
|
||||
<arg name="invalidates_properties" type="as" direction="out">
|
||||
</arg>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="io.hass.os.System">
|
||||
<method name="ScheduleWipeDevice">
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
<method name="WipeDevice">
|
||||
<arg type="b" direction="out">
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -2,8 +2,9 @@
|
||||
# pylint: disable=import-error,protected-access
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from supervisor.const import CoreState, HostFeature
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.host.const import HostFeature
|
||||
from supervisor.resolution.evaluations.systemd import EvaluateSystemd
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user