mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-13 04:06:33 +00:00
Check integritiy of core (#2851)
This commit is contained in:
parent
cee520f0b5
commit
682ee4529e
@ -37,7 +37,7 @@ from .dbus import DBusManager
|
|||||||
from .discovery import Discovery
|
from .discovery import Discovery
|
||||||
from .hardware.module import HardwareManager
|
from .hardware.module import HardwareManager
|
||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant.module import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
from .misc.filter import filter_data
|
from .misc.filter import filter_data
|
||||||
|
@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|||||||
from .discovery import Discovery
|
from .discovery import Discovery
|
||||||
from .hardware.module import HardwareManager
|
from .hardware.module import HardwareManager
|
||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant.module import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
from .jobs import JobManager
|
from .jobs import JobManager
|
||||||
|
@ -636,5 +636,7 @@ class DockerAddon(DockerInterface):
|
|||||||
self.sys_capture_exception(err)
|
self.sys_capture_exception(err)
|
||||||
super()._stop(remove_container)
|
super()._stop(remove_container)
|
||||||
|
|
||||||
def _validate_trust(self, image_id: str) -> None:
|
def _validate_trust(
|
||||||
|
self, image_id: str, image: str, version: AwesomeVersion
|
||||||
|
) -> None:
|
||||||
"""Validate trust of content."""
|
"""Validate trust of content."""
|
||||||
|
@ -3,17 +3,19 @@ from ipaddress import IPv4Address
|
|||||||
import logging
|
import logging
|
||||||
from typing import Awaitable, Dict, List, Optional
|
from typing import Awaitable, Dict, List, Optional
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion, AwesomeVersionCompare
|
||||||
import docker
|
import docker
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ..const import ENV_TIME, ENV_TOKEN, ENV_TOKEN_HASSIO, LABEL_MACHINE, MACHINE_ID
|
from ..const import ENV_TIME, ENV_TOKEN, ENV_TOKEN_HASSIO, LABEL_MACHINE, MACHINE_ID
|
||||||
from ..exceptions import DockerError
|
from ..exceptions import DockerError
|
||||||
from ..hardware.const import PolicyGroup
|
from ..hardware.const import PolicyGroup
|
||||||
|
from ..homeassistant.const import LANDINGPAGE
|
||||||
from .interface import CommandReturn, DockerInterface
|
from .interface import CommandReturn, DockerInterface
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
_VERIFY_TRUST: AwesomeVersion = AwesomeVersion("2021.1.0")
|
||||||
HASS_DOCKER_NAME = "homeassistant"
|
_HASS_DOCKER_NAME: str = "homeassistant"
|
||||||
|
|
||||||
|
|
||||||
class DockerHomeAssistant(DockerInterface):
|
class DockerHomeAssistant(DockerInterface):
|
||||||
@ -34,7 +36,7 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return name of Docker container."""
|
"""Return name of Docker container."""
|
||||||
return HASS_DOCKER_NAME
|
return _HASS_DOCKER_NAME
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self) -> int:
|
def timeout(self) -> int:
|
||||||
@ -211,5 +213,14 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _validate_trust(self, image_id: str) -> None:
|
def _validate_trust(
|
||||||
|
self, image_id: str, image: str, version: AwesomeVersion
|
||||||
|
) -> None:
|
||||||
"""Validate trust of content."""
|
"""Validate trust of content."""
|
||||||
|
try:
|
||||||
|
if version != LANDINGPAGE and version < _VERIFY_TRUST:
|
||||||
|
return
|
||||||
|
except AwesomeVersionCompare:
|
||||||
|
return
|
||||||
|
|
||||||
|
super()._validate_trust(image_id, image, version)
|
||||||
|
@ -175,7 +175,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
# Validate content
|
# Validate content
|
||||||
try:
|
try:
|
||||||
self._validate_trust(docker_image.id)
|
self._validate_trust(docker_image.id, image, version)
|
||||||
except CodeNotaryError:
|
except CodeNotaryError:
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
self.sys_docker.images.remove(
|
self.sys_docker.images.remove(
|
||||||
@ -190,7 +190,6 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
docker_image.tag(image, tag="latest")
|
docker_image.tag(image, tag="latest")
|
||||||
except docker.errors.APIError as err:
|
except docker.errors.APIError as err:
|
||||||
_LOGGER.error("Can't install %s:%s -> %s.", image, version, err)
|
|
||||||
if err.status_code == 429:
|
if err.status_code == 429:
|
||||||
self.sys_resolution.create_issue(
|
self.sys_resolution.create_issue(
|
||||||
IssueType.DOCKER_RATELIMIT,
|
IssueType.DOCKER_RATELIMIT,
|
||||||
@ -201,20 +200,22 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
"Your IP address has made too many requests to Docker Hub which activated a rate limit. "
|
"Your IP address has made too many requests to Docker Hub which activated a rate limit. "
|
||||||
"For more details see https://www.home-assistant.io/more-info/dockerhub-rate-limit"
|
"For more details see https://www.home-assistant.io/more-info/dockerhub-rate-limit"
|
||||||
)
|
)
|
||||||
raise DockerError() from err
|
raise DockerError(
|
||||||
|
f"Can't install {image}:{version!s}: {err}", _LOGGER.error
|
||||||
|
) from err
|
||||||
except (docker.errors.DockerException, requests.RequestException) as err:
|
except (docker.errors.DockerException, requests.RequestException) as err:
|
||||||
self.sys_capture_exception(err)
|
self.sys_capture_exception(err)
|
||||||
raise DockerError(
|
raise DockerError(
|
||||||
f"Unknown error with {image}:{version} -> {err!s}", _LOGGER.error
|
f"Unknown error with {image}:{version!s} -> {err!s}", _LOGGER.error
|
||||||
) from err
|
) from err
|
||||||
except CodeNotaryUntrusted as err:
|
except CodeNotaryUntrusted as err:
|
||||||
raise DockerTrustError(
|
raise DockerTrustError(
|
||||||
f"Pulled image {image}:{version} failed on content-trust verification!",
|
f"Pulled image {image}:{version!s} failed on content-trust verification!",
|
||||||
_LOGGER.critical,
|
_LOGGER.critical,
|
||||||
) from err
|
) from err
|
||||||
except CodeNotaryError as err:
|
except CodeNotaryError as err:
|
||||||
raise DockerTrustError(
|
raise DockerTrustError(
|
||||||
f"Error happened on Content-Trust check for {image}:{version}: {err!s}",
|
f"Error happened on Content-Trust check for {image}:{version!s}: {err!s}",
|
||||||
_LOGGER.error,
|
_LOGGER.error,
|
||||||
) from err
|
) from err
|
||||||
else:
|
else:
|
||||||
@ -614,7 +615,9 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return CommandReturn(code, output)
|
return CommandReturn(code, output)
|
||||||
|
|
||||||
def _validate_trust(self, image_id: str) -> None:
|
def _validate_trust(
|
||||||
|
self, image_id: str, image: str, version: AwesomeVersion
|
||||||
|
) -> None:
|
||||||
"""Validate trust of content."""
|
"""Validate trust of content."""
|
||||||
checksum = image_id.partition(":")[2]
|
checksum = image_id.partition(":")[2]
|
||||||
job = asyncio.run_coroutine_threadsafe(
|
job = asyncio.run_coroutine_threadsafe(
|
||||||
|
@ -1,258 +1 @@
|
|||||||
"""Home Assistant control object."""
|
"""Supervisor handler around core."""
|
||||||
import asyncio
|
|
||||||
from ipaddress import IPv4Address
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
|
||||||
from typing import Optional
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
|
||||||
|
|
||||||
from ..const import (
|
|
||||||
ATTR_ACCESS_TOKEN,
|
|
||||||
ATTR_AUDIO_INPUT,
|
|
||||||
ATTR_AUDIO_OUTPUT,
|
|
||||||
ATTR_BOOT,
|
|
||||||
ATTR_IMAGE,
|
|
||||||
ATTR_PORT,
|
|
||||||
ATTR_REFRESH_TOKEN,
|
|
||||||
ATTR_SSL,
|
|
||||||
ATTR_UUID,
|
|
||||||
ATTR_VERSION,
|
|
||||||
ATTR_WAIT_BOOT,
|
|
||||||
ATTR_WATCHDOG,
|
|
||||||
FILE_HASSIO_HOMEASSISTANT,
|
|
||||||
)
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
|
||||||
from ..utils.common import FileConfiguration
|
|
||||||
from .api import HomeAssistantAPI
|
|
||||||
from .core import HomeAssistantCore
|
|
||||||
from .secrets import HomeAssistantSecrets
|
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
|
||||||
from .websocket import HomeAssistantWebSocket
|
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(FileConfiguration, CoreSysAttributes):
|
|
||||||
"""Home Assistant core object for handle it."""
|
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
|
||||||
"""Initialize Home Assistant object."""
|
|
||||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
|
||||||
self.coresys: CoreSys = coresys
|
|
||||||
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
|
||||||
self._websocket: HomeAssistantWebSocket = HomeAssistantWebSocket(coresys)
|
|
||||||
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
|
||||||
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api(self) -> HomeAssistantAPI:
|
|
||||||
"""Return API handler for core."""
|
|
||||||
return self._api
|
|
||||||
|
|
||||||
@property
|
|
||||||
def websocket(self) -> HomeAssistantWebSocket:
|
|
||||||
"""Return Websocket handler for core."""
|
|
||||||
return self._websocket
|
|
||||||
|
|
||||||
@property
|
|
||||||
def core(self) -> HomeAssistantCore:
|
|
||||||
"""Return Core handler for docker."""
|
|
||||||
return self._core
|
|
||||||
|
|
||||||
@property
|
|
||||||
def secrets(self) -> HomeAssistantSecrets:
|
|
||||||
"""Return Secrets Manager for core."""
|
|
||||||
return self._secrets
|
|
||||||
|
|
||||||
@property
|
|
||||||
def machine(self) -> str:
|
|
||||||
"""Return the system machines."""
|
|
||||||
return self.core.instance.machine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arch(self) -> str:
|
|
||||||
"""Return arch of running Home Assistant."""
|
|
||||||
return self.core.instance.arch
|
|
||||||
|
|
||||||
@property
|
|
||||||
def error_state(self) -> bool:
|
|
||||||
"""Return True if system is in error."""
|
|
||||||
return self.core.error_state
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ip_address(self) -> IPv4Address:
|
|
||||||
"""Return IP of Home Assistant instance."""
|
|
||||||
return self.core.instance.ip_address
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_port(self) -> int:
|
|
||||||
"""Return network port to Home Assistant instance."""
|
|
||||||
return self._data[ATTR_PORT]
|
|
||||||
|
|
||||||
@api_port.setter
|
|
||||||
def api_port(self, value: int) -> None:
|
|
||||||
"""Set network port for Home Assistant instance."""
|
|
||||||
self._data[ATTR_PORT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_ssl(self) -> bool:
|
|
||||||
"""Return if we need ssl to Home Assistant instance."""
|
|
||||||
return self._data[ATTR_SSL]
|
|
||||||
|
|
||||||
@api_ssl.setter
|
|
||||||
def api_ssl(self, value: bool):
|
|
||||||
"""Set SSL for Home Assistant instance."""
|
|
||||||
self._data[ATTR_SSL] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_url(self) -> str:
|
|
||||||
"""Return API url to Home Assistant."""
|
|
||||||
return "{}://{}:{}".format(
|
|
||||||
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def watchdog(self) -> bool:
|
|
||||||
"""Return True if the watchdog should protect Home Assistant."""
|
|
||||||
return self._data[ATTR_WATCHDOG]
|
|
||||||
|
|
||||||
@watchdog.setter
|
|
||||||
def watchdog(self, value: bool):
|
|
||||||
"""Return True if the watchdog should protect Home Assistant."""
|
|
||||||
self._data[ATTR_WATCHDOG] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wait_boot(self) -> int:
|
|
||||||
"""Return time to wait for Home Assistant startup."""
|
|
||||||
return self._data[ATTR_WAIT_BOOT]
|
|
||||||
|
|
||||||
@wait_boot.setter
|
|
||||||
def wait_boot(self, value: int):
|
|
||||||
"""Set time to wait for Home Assistant startup."""
|
|
||||||
self._data[ATTR_WAIT_BOOT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latest_version(self) -> Optional[AwesomeVersion]:
|
|
||||||
"""Return last available version of Home Assistant."""
|
|
||||||
return self.sys_updater.version_homeassistant
|
|
||||||
|
|
||||||
@property
|
|
||||||
def image(self) -> str:
|
|
||||||
"""Return image name of the Home Assistant container."""
|
|
||||||
if self._data.get(ATTR_IMAGE):
|
|
||||||
return self._data[ATTR_IMAGE]
|
|
||||||
return f"homeassistant/{self.sys_machine}-homeassistant"
|
|
||||||
|
|
||||||
@image.setter
|
|
||||||
def image(self, value: str) -> None:
|
|
||||||
"""Set image name of Home Assistant container."""
|
|
||||||
self._data[ATTR_IMAGE] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def version(self) -> Optional[AwesomeVersion]:
|
|
||||||
"""Return version of local version."""
|
|
||||||
return self._data.get(ATTR_VERSION)
|
|
||||||
|
|
||||||
@version.setter
|
|
||||||
def version(self, value: AwesomeVersion) -> None:
|
|
||||||
"""Set installed version."""
|
|
||||||
self._data[ATTR_VERSION] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def boot(self) -> bool:
|
|
||||||
"""Return True if Home Assistant boot is enabled."""
|
|
||||||
return self._data[ATTR_BOOT]
|
|
||||||
|
|
||||||
@boot.setter
|
|
||||||
def boot(self, value: bool):
|
|
||||||
"""Set Home Assistant boot options."""
|
|
||||||
self._data[ATTR_BOOT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uuid(self) -> UUID:
|
|
||||||
"""Return a UUID of this Home Assistant instance."""
|
|
||||||
return self._data[ATTR_UUID]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supervisor_token(self) -> Optional[str]:
|
|
||||||
"""Return an access token for the Supervisor API."""
|
|
||||||
return self._data.get(ATTR_ACCESS_TOKEN)
|
|
||||||
|
|
||||||
@supervisor_token.setter
|
|
||||||
def supervisor_token(self, value: str) -> None:
|
|
||||||
"""Set the access token for the Supervisor API."""
|
|
||||||
self._data[ATTR_ACCESS_TOKEN] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def refresh_token(self) -> Optional[str]:
|
|
||||||
"""Return the refresh token to authenticate with Home Assistant."""
|
|
||||||
return self._data.get(ATTR_REFRESH_TOKEN)
|
|
||||||
|
|
||||||
@refresh_token.setter
|
|
||||||
def refresh_token(self, value: str):
|
|
||||||
"""Set Home Assistant refresh_token."""
|
|
||||||
self._data[ATTR_REFRESH_TOKEN] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path_pulse(self):
|
|
||||||
"""Return path to asound config."""
|
|
||||||
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path_extern_pulse(self):
|
|
||||||
"""Return path to asound config for Docker."""
|
|
||||||
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_output(self) -> Optional[str]:
|
|
||||||
"""Return a pulse profile for output or None."""
|
|
||||||
return self._data[ATTR_AUDIO_OUTPUT]
|
|
||||||
|
|
||||||
@audio_output.setter
|
|
||||||
def audio_output(self, value: Optional[str]):
|
|
||||||
"""Set audio output profile settings."""
|
|
||||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_input(self) -> Optional[str]:
|
|
||||||
"""Return pulse profile for input or None."""
|
|
||||||
return self._data[ATTR_AUDIO_INPUT]
|
|
||||||
|
|
||||||
@audio_input.setter
|
|
||||||
def audio_input(self, value: Optional[str]):
|
|
||||||
"""Set audio input settings."""
|
|
||||||
self._data[ATTR_AUDIO_INPUT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def need_update(self) -> bool:
|
|
||||||
"""Return true if a Home Assistant update is available."""
|
|
||||||
try:
|
|
||||||
return self.version < self.latest_version
|
|
||||||
except (AwesomeVersionException, TypeError):
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def load(self) -> None:
|
|
||||||
"""Prepare Home Assistant object."""
|
|
||||||
await asyncio.wait([self.secrets.load(), self.core.load()])
|
|
||||||
|
|
||||||
def write_pulse(self):
|
|
||||||
"""Write asound config to file and return True on success."""
|
|
||||||
pulse_config = self.sys_plugins.audio.pulse_client(
|
|
||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cleanup wrong maps
|
|
||||||
if self.path_pulse.is_dir():
|
|
||||||
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
|
||||||
|
|
||||||
# Write pulse config
|
|
||||||
try:
|
|
||||||
with self.path_pulse.open("w") as config_file:
|
|
||||||
config_file.write(pulse_config)
|
|
||||||
except OSError as err:
|
|
||||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
|
||||||
else:
|
|
||||||
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
|
||||||
|
258
supervisor/homeassistant/module.py
Normal file
258
supervisor/homeassistant/module.py
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
"""Home Assistant control object."""
|
||||||
|
import asyncio
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_ACCESS_TOKEN,
|
||||||
|
ATTR_AUDIO_INPUT,
|
||||||
|
ATTR_AUDIO_OUTPUT,
|
||||||
|
ATTR_BOOT,
|
||||||
|
ATTR_IMAGE,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_UUID,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WAIT_BOOT,
|
||||||
|
ATTR_WATCHDOG,
|
||||||
|
FILE_HASSIO_HOMEASSISTANT,
|
||||||
|
)
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..utils.common import FileConfiguration
|
||||||
|
from .api import HomeAssistantAPI
|
||||||
|
from .core import HomeAssistantCore
|
||||||
|
from .secrets import HomeAssistantSecrets
|
||||||
|
from .validate import SCHEMA_HASS_CONFIG
|
||||||
|
from .websocket import HomeAssistantWebSocket
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistant(FileConfiguration, CoreSysAttributes):
|
||||||
|
"""Home Assistant core object for handle it."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize Home Assistant object."""
|
||||||
|
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
||||||
|
self._websocket: HomeAssistantWebSocket = HomeAssistantWebSocket(coresys)
|
||||||
|
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
||||||
|
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api(self) -> HomeAssistantAPI:
|
||||||
|
"""Return API handler for core."""
|
||||||
|
return self._api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def websocket(self) -> HomeAssistantWebSocket:
|
||||||
|
"""Return Websocket handler for core."""
|
||||||
|
return self._websocket
|
||||||
|
|
||||||
|
@property
|
||||||
|
def core(self) -> HomeAssistantCore:
|
||||||
|
"""Return Core handler for docker."""
|
||||||
|
return self._core
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secrets(self) -> HomeAssistantSecrets:
|
||||||
|
"""Return Secrets Manager for core."""
|
||||||
|
return self._secrets
|
||||||
|
|
||||||
|
@property
|
||||||
|
def machine(self) -> str:
|
||||||
|
"""Return the system machines."""
|
||||||
|
return self.core.instance.machine
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arch(self) -> str:
|
||||||
|
"""Return arch of running Home Assistant."""
|
||||||
|
return self.core.instance.arch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_state(self) -> bool:
|
||||||
|
"""Return True if system is in error."""
|
||||||
|
return self.core.error_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip_address(self) -> IPv4Address:
|
||||||
|
"""Return IP of Home Assistant instance."""
|
||||||
|
return self.core.instance.ip_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_port(self) -> int:
|
||||||
|
"""Return network port to Home Assistant instance."""
|
||||||
|
return self._data[ATTR_PORT]
|
||||||
|
|
||||||
|
@api_port.setter
|
||||||
|
def api_port(self, value: int) -> None:
|
||||||
|
"""Set network port for Home Assistant instance."""
|
||||||
|
self._data[ATTR_PORT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_ssl(self) -> bool:
|
||||||
|
"""Return if we need ssl to Home Assistant instance."""
|
||||||
|
return self._data[ATTR_SSL]
|
||||||
|
|
||||||
|
@api_ssl.setter
|
||||||
|
def api_ssl(self, value: bool):
|
||||||
|
"""Set SSL for Home Assistant instance."""
|
||||||
|
self._data[ATTR_SSL] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_url(self) -> str:
|
||||||
|
"""Return API url to Home Assistant."""
|
||||||
|
return "{}://{}:{}".format(
|
||||||
|
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def watchdog(self) -> bool:
|
||||||
|
"""Return True if the watchdog should protect Home Assistant."""
|
||||||
|
return self._data[ATTR_WATCHDOG]
|
||||||
|
|
||||||
|
@watchdog.setter
|
||||||
|
def watchdog(self, value: bool):
|
||||||
|
"""Return True if the watchdog should protect Home Assistant."""
|
||||||
|
self._data[ATTR_WATCHDOG] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wait_boot(self) -> int:
|
||||||
|
"""Return time to wait for Home Assistant startup."""
|
||||||
|
return self._data[ATTR_WAIT_BOOT]
|
||||||
|
|
||||||
|
@wait_boot.setter
|
||||||
|
def wait_boot(self, value: int):
|
||||||
|
"""Set time to wait for Home Assistant startup."""
|
||||||
|
self._data[ATTR_WAIT_BOOT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_version(self) -> Optional[AwesomeVersion]:
|
||||||
|
"""Return last available version of Home Assistant."""
|
||||||
|
return self.sys_updater.version_homeassistant
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image(self) -> str:
|
||||||
|
"""Return image name of the Home Assistant container."""
|
||||||
|
if self._data.get(ATTR_IMAGE):
|
||||||
|
return self._data[ATTR_IMAGE]
|
||||||
|
return f"homeassistant/{self.sys_machine}-homeassistant"
|
||||||
|
|
||||||
|
@image.setter
|
||||||
|
def image(self, value: str) -> None:
|
||||||
|
"""Set image name of Home Assistant container."""
|
||||||
|
self._data[ATTR_IMAGE] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> Optional[AwesomeVersion]:
|
||||||
|
"""Return version of local version."""
|
||||||
|
return self._data.get(ATTR_VERSION)
|
||||||
|
|
||||||
|
@version.setter
|
||||||
|
def version(self, value: AwesomeVersion) -> None:
|
||||||
|
"""Set installed version."""
|
||||||
|
self._data[ATTR_VERSION] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boot(self) -> bool:
|
||||||
|
"""Return True if Home Assistant boot is enabled."""
|
||||||
|
return self._data[ATTR_BOOT]
|
||||||
|
|
||||||
|
@boot.setter
|
||||||
|
def boot(self, value: bool):
|
||||||
|
"""Set Home Assistant boot options."""
|
||||||
|
self._data[ATTR_BOOT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self) -> UUID:
|
||||||
|
"""Return a UUID of this Home Assistant instance."""
|
||||||
|
return self._data[ATTR_UUID]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supervisor_token(self) -> Optional[str]:
|
||||||
|
"""Return an access token for the Supervisor API."""
|
||||||
|
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||||
|
|
||||||
|
@supervisor_token.setter
|
||||||
|
def supervisor_token(self, value: str) -> None:
|
||||||
|
"""Set the access token for the Supervisor API."""
|
||||||
|
self._data[ATTR_ACCESS_TOKEN] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def refresh_token(self) -> Optional[str]:
|
||||||
|
"""Return the refresh token to authenticate with Home Assistant."""
|
||||||
|
return self._data.get(ATTR_REFRESH_TOKEN)
|
||||||
|
|
||||||
|
@refresh_token.setter
|
||||||
|
def refresh_token(self, value: str):
|
||||||
|
"""Set Home Assistant refresh_token."""
|
||||||
|
self._data[ATTR_REFRESH_TOKEN] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_pulse(self):
|
||||||
|
"""Return path to asound config."""
|
||||||
|
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_extern_pulse(self):
|
||||||
|
"""Return path to asound config for Docker."""
|
||||||
|
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio_output(self) -> Optional[str]:
|
||||||
|
"""Return a pulse profile for output or None."""
|
||||||
|
return self._data[ATTR_AUDIO_OUTPUT]
|
||||||
|
|
||||||
|
@audio_output.setter
|
||||||
|
def audio_output(self, value: Optional[str]):
|
||||||
|
"""Set audio output profile settings."""
|
||||||
|
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio_input(self) -> Optional[str]:
|
||||||
|
"""Return pulse profile for input or None."""
|
||||||
|
return self._data[ATTR_AUDIO_INPUT]
|
||||||
|
|
||||||
|
@audio_input.setter
|
||||||
|
def audio_input(self, value: Optional[str]):
|
||||||
|
"""Set audio input settings."""
|
||||||
|
self._data[ATTR_AUDIO_INPUT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def need_update(self) -> bool:
|
||||||
|
"""Return true if a Home Assistant update is available."""
|
||||||
|
try:
|
||||||
|
return self.version < self.latest_version
|
||||||
|
except (AwesomeVersionException, TypeError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def load(self) -> None:
|
||||||
|
"""Prepare Home Assistant object."""
|
||||||
|
await asyncio.wait([self.secrets.load(), self.core.load()])
|
||||||
|
|
||||||
|
def write_pulse(self):
|
||||||
|
"""Write asound config to file and return True on success."""
|
||||||
|
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||||
|
input_profile=self.audio_input, output_profile=self.audio_output
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cleanup wrong maps
|
||||||
|
if self.path_pulse.is_dir():
|
||||||
|
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
||||||
|
|
||||||
|
# Write pulse config
|
||||||
|
try:
|
||||||
|
with self.path_pulse.open("w") as config_file:
|
||||||
|
config_file.write(pulse_config)
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
Loading…
x
Reference in New Issue
Block a user