mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-10 21:49:35 +00:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c8cb8aecf7 | ||
![]() |
73e8875018 | ||
![]() |
02aed9c084 | ||
![]() |
89148f8fff | ||
![]() |
6bde527f5c | ||
![]() |
d62aabc01b | ||
![]() |
82299a3799 | ||
![]() |
c02f30dd7e | ||
![]() |
e91983adb4 | ||
![]() |
ff88359429 | ||
![]() |
5a60d5cbe8 | ||
![]() |
2b41ffe019 | ||
![]() |
1c23e26f93 | ||
![]() |
3d555f951d | ||
![]() |
6d39b4d7cd | ||
![]() |
4fe5d09f01 |
@@ -8,7 +8,7 @@ cryptography==2.8
|
|||||||
docker==4.2.0
|
docker==4.2.0
|
||||||
gitpython==3.1.0
|
gitpython==3.1.0
|
||||||
jinja2==2.11.1
|
jinja2==2.11.1
|
||||||
packaging==20.1
|
packaging==20.3
|
||||||
ptvsd==4.3.2
|
ptvsd==4.3.2
|
||||||
pulsectl==20.2.4
|
pulsectl==20.2.4
|
||||||
pytz==2019.3
|
pytz==2019.3
|
||||||
|
@@ -30,8 +30,7 @@ if __name__ == "__main__":
|
|||||||
loop = initialize_event_loop()
|
loop = initialize_event_loop()
|
||||||
|
|
||||||
# Check if all information are available to setup Supervisor
|
# Check if all information are available to setup Supervisor
|
||||||
if not bootstrap.check_environment():
|
bootstrap.check_environment()
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# init executor pool
|
# init executor pool
|
||||||
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
|
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
|
||||||
|
@@ -393,6 +393,11 @@ class Addon(AddonModel):
|
|||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
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:
|
try:
|
||||||
with self.path_pulse.open("w") as config_file:
|
with self.path_pulse.open("w") as config_file:
|
||||||
config_file.write(pulse_config)
|
config_file.write(pulse_config)
|
||||||
@@ -400,11 +405,10 @@ class Addon(AddonModel):
|
|||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Add-on %s can't write pulse/client.config: %s", self.slug, err
|
"Add-on %s can't write pulse/client.config: %s", self.slug, err
|
||||||
)
|
)
|
||||||
raise AddonsError()
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
_LOGGER.debug(
|
"Add-on %s write pulse/client.config: %s", self.slug, self.path_pulse
|
||||||
"Add-on %s write pulse/client.config: %s", self.slug, self.path_pulse
|
)
|
||||||
)
|
|
||||||
|
|
||||||
async def install_apparmor(self) -> None:
|
async def install_apparmor(self) -> None:
|
||||||
"""Install or Update AppArmor profile for Add-on."""
|
"""Install or Update AppArmor profile for Add-on."""
|
||||||
|
@@ -163,7 +163,7 @@ class APIAudio(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def set_profile(self, request: web.Request) -> None:
|
async def set_profile(self, request: web.Request) -> None:
|
||||||
"""Set audio default sources."""
|
"""Set audio default sources."""
|
||||||
body = await api_validate(SCHEMA_DEFAULT, request)
|
body = await api_validate(SCHEMA_PROFILE, request)
|
||||||
|
|
||||||
await asyncio.shield(
|
await asyncio.shield(
|
||||||
self.sys_host.sound.set_profile(body[ATTR_CARD], body[ATTR_NAME])
|
self.sys_host.sound.set_profile(body[ATTR_CARD], body[ATTR_NAME])
|
||||||
|
@@ -188,13 +188,6 @@ class Audio(JsonConfig, CoreSysAttributes):
|
|||||||
"""
|
"""
|
||||||
return self.instance.is_running()
|
return self.instance.is_running()
|
||||||
|
|
||||||
def is_fails(self) -> Awaitable[bool]:
|
|
||||||
"""Return True if a Docker container is fails state.
|
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
return self.instance.is_fails()
|
|
||||||
|
|
||||||
async def repair(self) -> None:
|
async def repair(self) -> None:
|
||||||
"""Repair CoreDNS plugin."""
|
"""Repair CoreDNS plugin."""
|
||||||
if await self.instance.exists():
|
if await self.instance.exists():
|
||||||
|
@@ -197,7 +197,7 @@ def initialize_logging():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_environment():
|
def check_environment() -> None:
|
||||||
"""Check if all environment are exists."""
|
"""Check if all environment are exists."""
|
||||||
# check environment variables
|
# check environment variables
|
||||||
for key in (ENV_SHARE, ENV_NAME, ENV_REPO):
|
for key in (ENV_SHARE, ENV_NAME, ENV_REPO):
|
||||||
@@ -205,24 +205,18 @@ def check_environment():
|
|||||||
os.environ[key]
|
os.environ[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
_LOGGER.fatal("Can't find %s in env!", key)
|
_LOGGER.fatal("Can't find %s in env!", key)
|
||||||
return False
|
|
||||||
|
|
||||||
# check docker socket
|
# check docker socket
|
||||||
if not SOCKET_DOCKER.is_socket():
|
if not SOCKET_DOCKER.is_socket():
|
||||||
_LOGGER.fatal("Can't find Docker socket!")
|
_LOGGER.fatal("Can't find Docker socket!")
|
||||||
return False
|
|
||||||
|
|
||||||
# check socat exec
|
# check socat exec
|
||||||
if not shutil.which("socat"):
|
if not shutil.which("socat"):
|
||||||
_LOGGER.fatal("Can't find socat!")
|
_LOGGER.fatal("Can't find socat!")
|
||||||
return False
|
|
||||||
|
|
||||||
# check socat exec
|
# check socat exec
|
||||||
if not shutil.which("gdbus"):
|
if not shutil.which("gdbus"):
|
||||||
_LOGGER.fatal("Can't find gdbus!")
|
_LOGGER.fatal("Can't find gdbus!")
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def reg_signal(loop):
|
def reg_signal(loop):
|
||||||
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
SUPERVISOR_VERSION = "205"
|
SUPERVISOR_VERSION = "209"
|
||||||
|
|
||||||
|
|
||||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||||
@@ -28,7 +28,7 @@ FILE_HASSIO_INGRESS = Path(SUPERVISOR_DATA, "ingress.json")
|
|||||||
FILE_HASSIO_DNS = Path(SUPERVISOR_DATA, "dns.json")
|
FILE_HASSIO_DNS = Path(SUPERVISOR_DATA, "dns.json")
|
||||||
FILE_HASSIO_AUDIO = Path(SUPERVISOR_DATA, "audio.json")
|
FILE_HASSIO_AUDIO = Path(SUPERVISOR_DATA, "audio.json")
|
||||||
|
|
||||||
SOCKET_DOCKER = Path("/var/run/docker.sock")
|
SOCKET_DOCKER = Path("/run/docker.sock")
|
||||||
|
|
||||||
DOCKER_NETWORK = "hassio"
|
DOCKER_NETWORK = "hassio"
|
||||||
DOCKER_NETWORK_MASK = ip_network("172.30.32.0/23")
|
DOCKER_NETWORK_MASK = ip_network("172.30.32.0/23")
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""Audio docker object."""
|
"""Audio docker object."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from ..const import ENV_TIME
|
from ..const import ENV_TIME
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
@@ -25,6 +27,22 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
|
|||||||
"""Return name of Docker container."""
|
"""Return name of Docker container."""
|
||||||
return AUDIO_DOCKER_NAME
|
return AUDIO_DOCKER_NAME
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volumes(self) -> Dict[str, Dict[str, str]]:
|
||||||
|
"""Return Volumes for the mount."""
|
||||||
|
volumes = {
|
||||||
|
str(self.sys_config.path_extern_audio): {"bind": "/data", "mode": "rw"},
|
||||||
|
"/etc/group": {"bind": "/host/group", "mode": "ro"},
|
||||||
|
}
|
||||||
|
|
||||||
|
# SND support
|
||||||
|
if Path("/dev/snd").exists():
|
||||||
|
volumes.update({"/dev/snd": {"bind": "/dev/snd", "mode": "rw"}})
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Kernel have no audio support in")
|
||||||
|
|
||||||
|
return volumes
|
||||||
|
|
||||||
def _run(self) -> None:
|
def _run(self) -> None:
|
||||||
"""Run Docker image.
|
"""Run Docker image.
|
||||||
|
|
||||||
@@ -48,14 +66,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
|
|||||||
detach=True,
|
detach=True,
|
||||||
privileged=True,
|
privileged=True,
|
||||||
environment={ENV_TIME: self.sys_timezone},
|
environment={ENV_TIME: self.sys_timezone},
|
||||||
volumes={
|
volumes=self.volumes,
|
||||||
str(self.sys_config.path_extern_audio): {
|
|
||||||
"bind": "/data",
|
|
||||||
"mode": "rw",
|
|
||||||
},
|
|
||||||
"/dev/snd": {"bind": "/dev/snd", "mode": "rw"},
|
|
||||||
"/etc/group": {"bind": "/host/group", "mode": "ro"},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._meta = docker_container.attrs
|
self._meta = docker_container.attrs
|
||||||
|
@@ -135,7 +135,17 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
detach=True,
|
detach=True,
|
||||||
stdout=True,
|
stdout=True,
|
||||||
stderr=True,
|
stderr=True,
|
||||||
volumes=self.volumes,
|
volumes={
|
||||||
|
str(self.sys_config.path_extern_homeassistant): {
|
||||||
|
"bind": "/config",
|
||||||
|
"mode": "rw",
|
||||||
|
},
|
||||||
|
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
|
||||||
|
str(self.sys_config.path_extern_share): {
|
||||||
|
"bind": "/share",
|
||||||
|
"mode": "ro",
|
||||||
|
},
|
||||||
|
},
|
||||||
environment={ENV_TIME: self.sys_timezone},
|
environment={ENV_TIME: self.sys_timezone},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
import shutil
|
||||||
import time
|
import time
|
||||||
from typing import Any, AsyncContextManager, Awaitable, Dict, Optional
|
from typing import Any, AsyncContextManager, Awaitable, Dict, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@@ -237,12 +238,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def path_pulse(self):
|
def path_pulse(self):
|
||||||
"""Return path to asound config."""
|
"""Return path to asound config."""
|
||||||
return Path(self.sys_config.path_tmp, f"homeassistant_pulse")
|
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_pulse(self):
|
def path_extern_pulse(self):
|
||||||
"""Return path to asound config for Docker."""
|
"""Return path to asound config for Docker."""
|
||||||
return Path(self.sys_config.path_extern_tmp, f"homeassistant_pulse")
|
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_output(self) -> Optional[str]:
|
def audio_output(self) -> Optional[str]:
|
||||||
@@ -644,11 +645,15 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
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:
|
try:
|
||||||
with self.path_pulse.open("w") as config_file:
|
with self.path_pulse.open("w") as config_file:
|
||||||
config_file.write(pulse_config)
|
config_file.write(pulse_config)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||||
raise HomeAssistantError()
|
else:
|
||||||
|
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
||||||
_LOGGER.debug("Home Assistant write pulse/client.config: %s", self.path_pulse)
|
|
||||||
|
@@ -73,7 +73,7 @@ class AppArmorControl(CoreSysAttributes):
|
|||||||
# Copy to AppArmor folder
|
# Copy to AppArmor folder
|
||||||
dest_profile = Path(self.sys_config.path_apparmor, profile_name)
|
dest_profile = Path(self.sys_config.path_apparmor, profile_name)
|
||||||
try:
|
try:
|
||||||
shutil.copy(profile_file, dest_profile)
|
shutil.copyfile(profile_file, dest_profile)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Can't copy %s: %s", profile_file, err)
|
_LOGGER.error("Can't copy %s: %s", profile_file, err)
|
||||||
raise HostAppArmorError() from None
|
raise HostAppArmorError() from None
|
||||||
|
@@ -19,20 +19,25 @@ class HwMonitor(CoreSysAttributes):
|
|||||||
"""Initialize Hardware Monitor object."""
|
"""Initialize Hardware Monitor object."""
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.context = pyudev.Context()
|
self.context = pyudev.Context()
|
||||||
self.monitor = pyudev.Monitor.from_netlink(self.context)
|
self.monitor: Optional[pyudev.Monitor] = None
|
||||||
self.observer: Optional[pyudev.MonitorObserver] = None
|
self.observer: Optional[pyudev.MonitorObserver] = None
|
||||||
|
|
||||||
async def load(self) -> None:
|
async def load(self) -> None:
|
||||||
"""Start hardware monitor."""
|
"""Start hardware monitor."""
|
||||||
self.observer = pyudev.MonitorObserver(self.monitor, self._udev_events)
|
try:
|
||||||
self.observer.start()
|
self.monitor = pyudev.Monitor.from_netlink(self.context)
|
||||||
|
self.observer = pyudev.MonitorObserver(self.monitor, self._udev_events)
|
||||||
_LOGGER.info("Start Supervisor hardware monitor")
|
except OSError:
|
||||||
|
_LOGGER.fatal("Not privileged to run udev. Update your installation!")
|
||||||
|
else:
|
||||||
|
self.observer.start()
|
||||||
|
_LOGGER.info("Started Supervisor hardware monitor")
|
||||||
|
|
||||||
async def unload(self) -> None:
|
async def unload(self) -> None:
|
||||||
"""Shutdown sessions."""
|
"""Shutdown sessions."""
|
||||||
if self.observer is None:
|
if self.observer is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.observer.stop()
|
self.observer.stop()
|
||||||
_LOGGER.info("Stop Supervisor hardware monitor")
|
_LOGGER.info("Stop Supervisor hardware monitor")
|
||||||
|
|
||||||
|
@@ -115,7 +115,7 @@ class Supervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
_LOGGER.info("Update Supervisor to version %s", version)
|
_LOGGER.info("Update Supervisor to version %s", version)
|
||||||
try:
|
try:
|
||||||
await self.instance.update(version, latest=True)
|
await self.instance.install(version, image=None, latest=True)
|
||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
_LOGGER.error("Update of Supervisor fails!")
|
_LOGGER.error("Update of Supervisor fails!")
|
||||||
raise SupervisorUpdateError() from None
|
raise SupervisorUpdateError() from None
|
||||||
|
@@ -228,7 +228,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
async def _watchdog_dns_docker(self):
|
async def _watchdog_dns_docker(self):
|
||||||
"""Check running state of Docker and start if they is close."""
|
"""Check running state of Docker and start if they is close."""
|
||||||
# if CoreDNS is active
|
# if CoreDNS is active
|
||||||
if await self.sys_dns.is_running():
|
if await self.sys_dns.is_running() or self.sys_dns.in_progress:
|
||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
async def _watchdog_audio_docker(self):
|
async def _watchdog_audio_docker(self):
|
||||||
"""Check running state of Docker and start if they is close."""
|
"""Check running state of Docker and start if they is close."""
|
||||||
# if PulseAudio plugin is active
|
# if PulseAudio plugin is active
|
||||||
if await self.sys_audio.is_running():
|
if await self.sys_audio.is_running() or self.sys_audio.in_progress:
|
||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with PulseAudio plugin!")
|
_LOGGER.warning("Watchdog found a problem with PulseAudio plugin!")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user