mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 23:26:29 +00:00
Multicast support on Hass.io Network (#1634)
* Add multicast layer to docker * support network forward * add pluginmanager * finish multicast plugin * fix lint * Add shutdown for plugins * Add API * Add watchdog * Fix black * Fix path
This commit is contained in:
parent
2364e1e652
commit
f0ed2eba2b
36
API.md
36
API.md
@ -935,6 +935,42 @@ return:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multicast
|
||||||
|
|
||||||
|
- GET `/multicast/info`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"version_latest": "2"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- POST `/multicast/update`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "VERSION"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- POST `/multicast/restart`
|
||||||
|
|
||||||
|
- GET `/multicast/stats`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_usage": 283123,
|
||||||
|
"memory_limit": 329392,
|
||||||
|
"memory_percent": 1.4,
|
||||||
|
"network_tx": 0,
|
||||||
|
"network_rx": 0,
|
||||||
|
"blk_read": 0,
|
||||||
|
"blk_write": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Auth / SSO API
|
### Auth / SSO API
|
||||||
|
|
||||||
You can use the user system on homeassistant. We handle this auth system on
|
You can use the user system on homeassistant. We handle this auth system on
|
||||||
|
@ -325,10 +325,10 @@ class AddonManager(CoreSysAttributes):
|
|||||||
for addon in self.installed:
|
for addon in self.installed:
|
||||||
if not await addon.instance.is_running():
|
if not await addon.instance.is_running():
|
||||||
continue
|
continue
|
||||||
self.sys_dns.add_host(
|
self.sys_plugins.dns.add_host(
|
||||||
ipv4=addon.ip_address, names=[addon.hostname], write=False
|
ipv4=addon.ip_address, names=[addon.hostname], write=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write hosts files
|
# Write hosts files
|
||||||
with suppress(CoreDNSError):
|
with suppress(CoreDNSError):
|
||||||
self.sys_dns.write_hosts()
|
self.sys_plugins.dns.write_hosts()
|
||||||
|
@ -389,7 +389,7 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
def write_pulse(self):
|
def write_pulse(self):
|
||||||
"""Write asound config to file and return True on success."""
|
"""Write asound config to file and return True on success."""
|
||||||
pulse_config = self.sys_audio.pulse_client(
|
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
input_profile=self.audio_input, output_profile=self.audio_output
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ from .security import SecurityMiddleware
|
|||||||
from .services import APIServices
|
from .services import APIServices
|
||||||
from .snapshots import APISnapshots
|
from .snapshots import APISnapshots
|
||||||
from .supervisor import APISupervisor
|
from .supervisor import APISupervisor
|
||||||
|
from .multicast import APIMulticast
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_host()
|
self._register_host()
|
||||||
self._register_os()
|
self._register_os()
|
||||||
self._register_cli()
|
self._register_cli()
|
||||||
|
self._register_multicast()
|
||||||
self._register_hardware()
|
self._register_hardware()
|
||||||
self._register_homeassistant()
|
self._register_homeassistant()
|
||||||
self._register_proxy()
|
self._register_proxy()
|
||||||
@ -113,6 +115,20 @@ class RestAPI(CoreSysAttributes):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _register_multicast(self) -> None:
|
||||||
|
"""Register Multicast functions."""
|
||||||
|
api_multicast = APIMulticast()
|
||||||
|
api_multicast.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[
|
||||||
|
web.get("/multicast/info", api_multicast.info),
|
||||||
|
web.get("/multicast/stats", api_multicast.stats),
|
||||||
|
web.post("/multicast/update", api_multicast.update),
|
||||||
|
web.post("/multicast/restart", api_multicast.restart),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def _register_hardware(self) -> None:
|
def _register_hardware(self) -> None:
|
||||||
"""Register hardware functions."""
|
"""Register hardware functions."""
|
||||||
api_hardware = APIHardware()
|
api_hardware = APIHardware()
|
||||||
|
@ -68,8 +68,8 @@ class APIAudio(CoreSysAttributes):
|
|||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return Audio information."""
|
"""Return Audio information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_audio.version,
|
ATTR_VERSION: self.sys_plugins.audio.version,
|
||||||
ATTR_VERSION_LATEST: self.sys_audio.latest_version,
|
ATTR_VERSION_LATEST: self.sys_plugins.audio.latest_version,
|
||||||
ATTR_HOST: str(self.sys_docker.network.audio),
|
ATTR_HOST: str(self.sys_docker.network.audio),
|
||||||
ATTR_AUDIO: {
|
ATTR_AUDIO: {
|
||||||
ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards],
|
ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards],
|
||||||
@ -88,7 +88,7 @@ class APIAudio(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_audio.stats()
|
stats = await self.sys_plugins.audio.stats()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@ -105,21 +105,21 @@ class APIAudio(CoreSysAttributes):
|
|||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update Audio plugin."""
|
"""Update Audio plugin."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_audio.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_plugins.audio.latest_version)
|
||||||
|
|
||||||
if version == self.sys_audio.version:
|
if version == self.sys_plugins.audio.version:
|
||||||
raise APIError("Version {} is already in use".format(version))
|
raise APIError("Version {} is already in use".format(version))
|
||||||
await asyncio.shield(self.sys_audio.update(version))
|
await asyncio.shield(self.sys_plugins.audio.update(version))
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return Audio Docker logs."""
|
"""Return Audio Docker logs."""
|
||||||
return self.sys_audio.logs()
|
return self.sys_plugins.audio.logs()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart Audio plugin."""
|
"""Restart Audio plugin."""
|
||||||
return asyncio.shield(self.sys_audio.restart())
|
return asyncio.shield(self.sys_plugins.audio.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def reload(self, request: web.Request) -> Awaitable[None]:
|
def reload(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
@ -33,14 +33,14 @@ class APICli(CoreSysAttributes):
|
|||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return HA cli information."""
|
"""Return HA cli information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_cli.version,
|
ATTR_VERSION: self.sys_plugins.cli.version,
|
||||||
ATTR_VERSION_LATEST: self.sys_cli.latest_version,
|
ATTR_VERSION_LATEST: self.sys_plugins.cli.latest_version,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_cli.stats()
|
stats = await self.sys_plugins.cli.stats()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@ -57,6 +57,6 @@ class APICli(CoreSysAttributes):
|
|||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update HA CLI."""
|
"""Update HA CLI."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_cli.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_plugins.cli.latest_version)
|
||||||
|
|
||||||
await asyncio.shield(self.sys_cli.update(version))
|
await asyncio.shield(self.sys_plugins.cli.update(version))
|
||||||
|
@ -42,10 +42,10 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return DNS information."""
|
"""Return DNS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_dns.version,
|
ATTR_VERSION: self.sys_plugins.dns.version,
|
||||||
ATTR_VERSION_LATEST: self.sys_dns.latest_version,
|
ATTR_VERSION_LATEST: self.sys_plugins.dns.latest_version,
|
||||||
ATTR_HOST: str(self.sys_docker.network.dns),
|
ATTR_HOST: str(self.sys_docker.network.dns),
|
||||||
ATTR_SERVERS: self.sys_dns.servers,
|
ATTR_SERVERS: self.sys_plugins.dns.servers,
|
||||||
ATTR_LOCALS: self.sys_host.network.dns_servers,
|
ATTR_LOCALS: self.sys_host.network.dns_servers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,15 +55,15 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
if ATTR_SERVERS in body:
|
if ATTR_SERVERS in body:
|
||||||
self.sys_dns.servers = body[ATTR_SERVERS]
|
self.sys_plugins.dns.servers = body[ATTR_SERVERS]
|
||||||
self.sys_create_task(self.sys_dns.restart())
|
self.sys_create_task(self.sys_plugins.dns.restart())
|
||||||
|
|
||||||
self.sys_dns.save_data()
|
self.sys_plugins.dns.save_data()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_dns.stats()
|
stats = await self.sys_plugins.dns.stats()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@ -80,23 +80,23 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update DNS plugin."""
|
"""Update DNS plugin."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_dns.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_plugins.dns.latest_version)
|
||||||
|
|
||||||
if version == self.sys_dns.version:
|
if version == self.sys_plugins.dns.version:
|
||||||
raise APIError("Version {} is already in use".format(version))
|
raise APIError("Version {} is already in use".format(version))
|
||||||
await asyncio.shield(self.sys_dns.update(version))
|
await asyncio.shield(self.sys_plugins.dns.update(version))
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return DNS Docker logs."""
|
"""Return DNS Docker logs."""
|
||||||
return self.sys_dns.logs()
|
return self.sys_plugins.dns.logs()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart CoreDNS plugin."""
|
"""Restart CoreDNS plugin."""
|
||||||
return asyncio.shield(self.sys_dns.restart())
|
return asyncio.shield(self.sys_plugins.dns.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def reset(self, request: web.Request) -> Awaitable[None]:
|
def reset(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Reset CoreDNS plugin."""
|
"""Reset CoreDNS plugin."""
|
||||||
return asyncio.shield(self.sys_dns.reset())
|
return asyncio.shield(self.sys_plugins.dns.reset())
|
||||||
|
76
supervisor/api/multicast.py
Normal file
76
supervisor/api/multicast.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""Init file for Supervisor Multicast RESTful API."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_BLK_READ,
|
||||||
|
ATTR_BLK_WRITE,
|
||||||
|
ATTR_CPU_PERCENT,
|
||||||
|
ATTR_VERSION_LATEST,
|
||||||
|
ATTR_MEMORY_LIMIT,
|
||||||
|
ATTR_MEMORY_PERCENT,
|
||||||
|
ATTR_MEMORY_USAGE,
|
||||||
|
ATTR_NETWORK_RX,
|
||||||
|
ATTR_NETWORK_TX,
|
||||||
|
ATTR_VERSION,
|
||||||
|
CONTENT_TYPE_BINARY,
|
||||||
|
)
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APIError
|
||||||
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
|
|
||||||
|
|
||||||
|
class APIMulticast(CoreSysAttributes):
|
||||||
|
"""Handle RESTful API for Multicast functions."""
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
|
"""Return Multicast information."""
|
||||||
|
return {
|
||||||
|
ATTR_VERSION: self.sys_plugins.multicast.version,
|
||||||
|
ATTR_VERSION_LATEST: self.sys_plugins.multicast.latest_version,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
|
"""Return resource information."""
|
||||||
|
stats = await self.sys_plugins.multicast.stats()
|
||||||
|
|
||||||
|
return {
|
||||||
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
|
ATTR_MEMORY_USAGE: stats.memory_usage,
|
||||||
|
ATTR_MEMORY_LIMIT: stats.memory_limit,
|
||||||
|
ATTR_MEMORY_PERCENT: stats.memory_percent,
|
||||||
|
ATTR_NETWORK_RX: stats.network_rx,
|
||||||
|
ATTR_NETWORK_TX: stats.network_tx,
|
||||||
|
ATTR_BLK_READ: stats.blk_read,
|
||||||
|
ATTR_BLK_WRITE: stats.blk_write,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def update(self, request: web.Request) -> None:
|
||||||
|
"""Update Multicast plugin."""
|
||||||
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
|
version = body.get(ATTR_VERSION, self.sys_plugins.multicast.latest_version)
|
||||||
|
|
||||||
|
if version == self.sys_plugins.multicast.version:
|
||||||
|
raise APIError("Version {} is already in use".format(version))
|
||||||
|
await asyncio.shield(self.sys_plugins.multicast.update(version))
|
||||||
|
|
||||||
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
|
"""Return Multicast Docker logs."""
|
||||||
|
return self.sys_plugins.multicast.logs()
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
"""Restart Multicast plugin."""
|
||||||
|
return asyncio.shield(self.sys_plugins.multicast.restart())
|
@ -130,7 +130,7 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
|
|
||||||
# Host
|
# Host
|
||||||
# Remove machine_id handling later if all use new CLI
|
# Remove machine_id handling later if all use new CLI
|
||||||
if supervisor_token in (self.sys_machine_id, self.sys_cli.supervisor_token):
|
if supervisor_token in (self.sys_machine_id, self.sys_plugins.cli.supervisor_token):
|
||||||
_LOGGER.debug("%s access from Host", request.path)
|
_LOGGER.debug("%s access from Host", request.path)
|
||||||
request_from = self.sys_host
|
request_from = self.sys_host
|
||||||
|
|
||||||
|
@ -35,10 +35,8 @@ from .supervisor import Supervisor
|
|||||||
from .tasks import Tasks
|
from .tasks import Tasks
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .secrets import SecretsManager
|
from .secrets import SecretsManager
|
||||||
|
from .plugins import PluginManager
|
||||||
from .utils.dt import fetch_timezone
|
from .utils.dt import fetch_timezone
|
||||||
from .plugins.dns import CoreDNS
|
|
||||||
from .plugins.cli import HaCli
|
|
||||||
from .plugins.audio import Audio
|
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -52,9 +50,8 @@ async def initialize_coresys():
|
|||||||
|
|
||||||
# Initialize core objects
|
# Initialize core objects
|
||||||
coresys.core = Core(coresys)
|
coresys.core = Core(coresys)
|
||||||
coresys.dns = CoreDNS(coresys)
|
coresys.plugins = PluginManager(coresys)
|
||||||
coresys.arch = CpuArch(coresys)
|
coresys.arch = CpuArch(coresys)
|
||||||
coresys.audio = Audio(coresys)
|
|
||||||
coresys.auth = Auth(coresys)
|
coresys.auth = Auth(coresys)
|
||||||
coresys.updater = Updater(coresys)
|
coresys.updater = Updater(coresys)
|
||||||
coresys.api = RestAPI(coresys)
|
coresys.api = RestAPI(coresys)
|
||||||
@ -72,7 +69,6 @@ async def initialize_coresys():
|
|||||||
coresys.dbus = DBusManager(coresys)
|
coresys.dbus = DBusManager(coresys)
|
||||||
coresys.hassos = HassOS(coresys)
|
coresys.hassos = HassOS(coresys)
|
||||||
coresys.secrets = SecretsManager(coresys)
|
coresys.secrets = SecretsManager(coresys)
|
||||||
coresys.cli = HaCli(coresys)
|
|
||||||
|
|
||||||
# bootstrap config
|
# bootstrap config
|
||||||
initialize_system_data(coresys)
|
initialize_system_data(coresys)
|
||||||
|
@ -28,6 +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")
|
||||||
FILE_HASSIO_CLI = Path(SUPERVISOR_DATA, "cli.json")
|
FILE_HASSIO_CLI = Path(SUPERVISOR_DATA, "cli.json")
|
||||||
|
FILE_HASSIO_MULTICAST = Path(SUPERVISOR_DATA, "multicast.json")
|
||||||
|
|
||||||
SOCKET_DOCKER = Path("/run/docker.sock")
|
SOCKET_DOCKER = Path("/run/docker.sock")
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ HEADER_TOKEN_OLD = "X-Hassio-Key"
|
|||||||
ENV_TOKEN_OLD = "HASSIO_TOKEN"
|
ENV_TOKEN_OLD = "HASSIO_TOKEN"
|
||||||
ENV_TOKEN = "SUPERVISOR_TOKEN"
|
ENV_TOKEN = "SUPERVISOR_TOKEN"
|
||||||
ENV_TIME = "TZ"
|
ENV_TIME = "TZ"
|
||||||
|
ENV_HASSIO_NETWORK = "HASSIO_NETWORK"
|
||||||
|
|
||||||
ENV_HOMEASSISTANT_REPOSITORY = "HOMEASSISTANT_REPOSITORY"
|
ENV_HOMEASSISTANT_REPOSITORY = "HOMEASSISTANT_REPOSITORY"
|
||||||
ENV_SUPERVISOR_SHARE = "SUPERVISOR_SHARE"
|
ENV_SUPERVISOR_SHARE = "SUPERVISOR_SHARE"
|
||||||
@ -77,6 +79,7 @@ REQUEST_FROM = "HASSIO_FROM"
|
|||||||
|
|
||||||
ATTR_SUPERVISOR = "supervisor"
|
ATTR_SUPERVISOR = "supervisor"
|
||||||
ATTR_MACHINE = "machine"
|
ATTR_MACHINE = "machine"
|
||||||
|
ATTR_MULTICAST = "multicast"
|
||||||
ATTR_WAIT_BOOT = "wait_boot"
|
ATTR_WAIT_BOOT = "wait_boot"
|
||||||
ATTR_DEPLOYMENT = "deployment"
|
ATTR_DEPLOYMENT = "deployment"
|
||||||
ATTR_WATCHDOG = "watchdog"
|
ATTR_WATCHDOG = "watchdog"
|
||||||
|
@ -41,9 +41,7 @@ class Core(CoreSysAttributes):
|
|||||||
await self.sys_host.load()
|
await self.sys_host.load()
|
||||||
|
|
||||||
# Load Plugins container
|
# Load Plugins container
|
||||||
await asyncio.wait(
|
await self.sys_plugins.load()
|
||||||
[self.sys_dns.load(), self.sys_audio.load(), self.sys_cli.load()]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Load Home Assistant
|
# Load Home Assistant
|
||||||
await self.sys_homeassistant.load()
|
await self.sys_homeassistant.load()
|
||||||
@ -172,7 +170,7 @@ class Core(CoreSysAttributes):
|
|||||||
self.sys_websession.close(),
|
self.sys_websession.close(),
|
||||||
self.sys_websession_ssl.close(),
|
self.sys_websession_ssl.close(),
|
||||||
self.sys_ingress.unload(),
|
self.sys_ingress.unload(),
|
||||||
self.sys_dns.unload(),
|
self.sys_plugins.unload(),
|
||||||
self.sys_hwmonitor.unload(),
|
self.sys_hwmonitor.unload(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -193,6 +191,9 @@ class Core(CoreSysAttributes):
|
|||||||
await self.sys_addons.shutdown(STARTUP_SYSTEM)
|
await self.sys_addons.shutdown(STARTUP_SYSTEM)
|
||||||
await self.sys_addons.shutdown(STARTUP_INITIALIZE)
|
await self.sys_addons.shutdown(STARTUP_INITIALIZE)
|
||||||
|
|
||||||
|
# Shutdown all Plugins
|
||||||
|
await self.sys_plugins.shutdown()
|
||||||
|
|
||||||
def _update_last_boot(self):
|
def _update_last_boot(self):
|
||||||
"""Update last boot time."""
|
"""Update last boot time."""
|
||||||
self.sys_config.last_boot = self.sys_hardware.last_boot
|
self.sys_config.last_boot = self.sys_hardware.last_boot
|
||||||
@ -204,9 +205,7 @@ class Core(CoreSysAttributes):
|
|||||||
await self.sys_run_in_executor(self.sys_docker.repair)
|
await self.sys_run_in_executor(self.sys_docker.repair)
|
||||||
|
|
||||||
# Fix plugins
|
# Fix plugins
|
||||||
await asyncio.wait(
|
await self.sys_plugins.repair()
|
||||||
[self.sys_dns.repair(), self.sys_audio.repair(), self.sys_cli.repair()]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Restore core functionality
|
# Restore core functionality
|
||||||
await self.sys_addons.repair()
|
await self.sys_addons.repair()
|
||||||
|
@ -31,9 +31,7 @@ if TYPE_CHECKING:
|
|||||||
from .store import StoreManager
|
from .store import StoreManager
|
||||||
from .tasks import Tasks
|
from .tasks import Tasks
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .plugins.cli import HaCli
|
from .plugins import PluginManager
|
||||||
from .plugins.audio import Audio
|
|
||||||
from .plugins.dns import CoreDNS
|
|
||||||
|
|
||||||
|
|
||||||
class CoreSys:
|
class CoreSys:
|
||||||
@ -61,10 +59,7 @@ class CoreSys:
|
|||||||
# Internal objects pointers
|
# Internal objects pointers
|
||||||
self._core: Optional[Core] = None
|
self._core: Optional[Core] = None
|
||||||
self._arch: Optional[CpuArch] = None
|
self._arch: Optional[CpuArch] = None
|
||||||
self._audio: Optional[Audio] = None
|
|
||||||
self._auth: Optional[Auth] = None
|
self._auth: Optional[Auth] = None
|
||||||
self._dns: Optional[CoreDNS] = None
|
|
||||||
self._cli: Optional[HaCli] = None
|
|
||||||
self._homeassistant: Optional[HomeAssistant] = None
|
self._homeassistant: Optional[HomeAssistant] = None
|
||||||
self._supervisor: Optional[Supervisor] = None
|
self._supervisor: Optional[Supervisor] = None
|
||||||
self._addons: Optional[AddonManager] = None
|
self._addons: Optional[AddonManager] = None
|
||||||
@ -81,6 +76,7 @@ class CoreSys:
|
|||||||
self._store: Optional[StoreManager] = None
|
self._store: Optional[StoreManager] = None
|
||||||
self._discovery: Optional[Discovery] = None
|
self._discovery: Optional[Discovery] = None
|
||||||
self._hwmonitor: Optional[HwMonitor] = None
|
self._hwmonitor: Optional[HwMonitor] = None
|
||||||
|
self._plugins: Optional[PluginManager] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dev(self) -> bool:
|
def dev(self) -> bool:
|
||||||
@ -140,16 +136,16 @@ class CoreSys:
|
|||||||
self._core = value
|
self._core = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cli(self) -> HaCli:
|
def plugins(self) -> PluginManager:
|
||||||
"""Return HaCli object."""
|
"""Return PluginManager object."""
|
||||||
return self._cli
|
return self._plugins
|
||||||
|
|
||||||
@cli.setter
|
@plugins.setter
|
||||||
def cli(self, value: HaCli):
|
def plugins(self, value: PluginManager):
|
||||||
"""Set a HaCli object."""
|
"""Set a PluginManager object."""
|
||||||
if self._cli:
|
if self._plugins:
|
||||||
raise RuntimeError("HaCli already set!")
|
raise RuntimeError("PluginManager already set!")
|
||||||
self._cli = value
|
self._plugins = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self) -> CpuArch:
|
def arch(self) -> CpuArch:
|
||||||
@ -175,18 +171,6 @@ class CoreSys:
|
|||||||
raise RuntimeError("Auth already set!")
|
raise RuntimeError("Auth already set!")
|
||||||
self._auth = value
|
self._auth = value
|
||||||
|
|
||||||
@property
|
|
||||||
def audio(self) -> Audio:
|
|
||||||
"""Return Audio object."""
|
|
||||||
return self._audio
|
|
||||||
|
|
||||||
@audio.setter
|
|
||||||
def audio(self, value: Audio):
|
|
||||||
"""Set a Audio object."""
|
|
||||||
if self._audio:
|
|
||||||
raise RuntimeError("Audio already set!")
|
|
||||||
self._audio = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant(self) -> HomeAssistant:
|
def homeassistant(self) -> HomeAssistant:
|
||||||
"""Return Home Assistant object."""
|
"""Return Home Assistant object."""
|
||||||
@ -331,18 +315,6 @@ class CoreSys:
|
|||||||
raise RuntimeError("DBusManager already set!")
|
raise RuntimeError("DBusManager already set!")
|
||||||
self._dbus = value
|
self._dbus = value
|
||||||
|
|
||||||
@property
|
|
||||||
def dns(self) -> CoreDNS:
|
|
||||||
"""Return CoreDNS object."""
|
|
||||||
return self._dns
|
|
||||||
|
|
||||||
@dns.setter
|
|
||||||
def dns(self, value: CoreDNS):
|
|
||||||
"""Set a CoreDNS object."""
|
|
||||||
if self._dns:
|
|
||||||
raise RuntimeError("CoreDNS already set!")
|
|
||||||
self._dns = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self) -> HostManager:
|
def host(self) -> HostManager:
|
||||||
"""Return HostManager object."""
|
"""Return HostManager object."""
|
||||||
@ -482,9 +454,9 @@ class CoreSysAttributes:
|
|||||||
return self.coresys.core
|
return self.coresys.core
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_cli(self) -> HaCli:
|
def sys_plugins(self) -> PluginManager:
|
||||||
"""Return HaCli object."""
|
"""Return PluginManager object."""
|
||||||
return self.coresys.cli
|
return self.coresys.plugins
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_arch(self) -> CpuArch:
|
def sys_arch(self) -> CpuArch:
|
||||||
@ -496,11 +468,6 @@ class CoreSysAttributes:
|
|||||||
"""Return Auth object."""
|
"""Return Auth object."""
|
||||||
return self.coresys.auth
|
return self.coresys.auth
|
||||||
|
|
||||||
@property
|
|
||||||
def sys_audio(self) -> Audio:
|
|
||||||
"""Return Audio object."""
|
|
||||||
return self.coresys.audio
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_homeassistant(self) -> HomeAssistant:
|
def sys_homeassistant(self) -> HomeAssistant:
|
||||||
"""Return Home Assistant object."""
|
"""Return Home Assistant object."""
|
||||||
@ -561,11 +528,6 @@ class CoreSysAttributes:
|
|||||||
"""Return DBusManager object."""
|
"""Return DBusManager object."""
|
||||||
return self.coresys.dbus
|
return self.coresys.dbus
|
||||||
|
|
||||||
@property
|
|
||||||
def sys_dns(self) -> CoreDNS:
|
|
||||||
"""Return CoreDNS object."""
|
|
||||||
return self.coresys.dns
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_host(self) -> HostManager:
|
def sys_host(self) -> HostManager:
|
||||||
"""Return HostManager object."""
|
"""Return HostManager object."""
|
||||||
|
@ -308,11 +308,11 @@ class DockerAddon(DockerInterface):
|
|||||||
"bind": "/etc/pulse/client.conf",
|
"bind": "/etc/pulse/client.conf",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
str(self.sys_audio.path_extern_pulse): {
|
str(self.sys_plugins.audio.path_extern_pulse): {
|
||||||
"bind": "/run/audio",
|
"bind": "/run/audio",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
str(self.sys_audio.path_extern_asound): {
|
str(self.sys_plugins.audio.path_extern_asound): {
|
||||||
"bind": "/etc/asound.conf",
|
"bind": "/etc/asound.conf",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
@ -364,7 +364,7 @@ class DockerAddon(DockerInterface):
|
|||||||
_LOGGER.info("Start Docker add-on %s with version %s", self.image, self.version)
|
_LOGGER.info("Start Docker add-on %s with version %s", self.image, self.version)
|
||||||
|
|
||||||
# Write data to DNS server
|
# Write data to DNS server
|
||||||
self.sys_dns.add_host(ipv4=self.ip_address, names=[self.addon.hostname])
|
self.sys_plugins.dns.add_host(ipv4=self.ip_address, names=[self.addon.hostname])
|
||||||
|
|
||||||
def _install(
|
def _install(
|
||||||
self, tag: str, image: Optional[str] = None, latest: bool = False
|
self, tag: str, image: Optional[str] = None, latest: bool = False
|
||||||
@ -490,5 +490,5 @@ class DockerAddon(DockerInterface):
|
|||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if self.ip_address != NO_ADDDRESS:
|
if self.ip_address != NO_ADDDRESS:
|
||||||
self.sys_dns.delete_host(self.addon.hostname)
|
self.sys_plugins.dns.delete_host(self.addon.hostname)
|
||||||
super()._stop(remove_container)
|
super()._stop(remove_container)
|
||||||
|
@ -20,7 +20,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def image(self) -> str:
|
def image(self) -> str:
|
||||||
"""Return name of Supervisor Audio image."""
|
"""Return name of Supervisor Audio image."""
|
||||||
return self.sys_audio.image
|
return self.sys_plugins.audio.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -58,7 +58,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
|
|||||||
# Create & Run container
|
# Create & Run container
|
||||||
docker_container = self.sys_docker.run(
|
docker_container = self.sys_docker.run(
|
||||||
self.image,
|
self.image,
|
||||||
version=self.sys_audio.version,
|
version=self.sys_plugins.audio.version,
|
||||||
init=False,
|
init=False,
|
||||||
ipv4=self.sys_docker.network.audio,
|
ipv4=self.sys_docker.network.audio,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
|
@ -18,7 +18,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of HA cli image."""
|
"""Return name of HA cli image."""
|
||||||
return self.sys_cli.image
|
return self.sys_plugins.cli.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -42,7 +42,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
|
|||||||
self.image,
|
self.image,
|
||||||
entrypoint=["/init"],
|
entrypoint=["/init"],
|
||||||
command=["/bin/bash", "-c", "sleep infinity"],
|
command=["/bin/bash", "-c", "sleep infinity"],
|
||||||
version=self.sys_cli.version,
|
version=self.sys_plugins.cli.version,
|
||||||
init=False,
|
init=False,
|
||||||
ipv4=self.sys_docker.network.cli,
|
ipv4=self.sys_docker.network.cli,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
@ -51,7 +51,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
|
|||||||
extra_hosts={"supervisor": self.sys_docker.network.supervisor},
|
extra_hosts={"supervisor": self.sys_docker.network.supervisor},
|
||||||
environment={
|
environment={
|
||||||
ENV_TIME: self.sys_timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
ENV_TOKEN: self.sys_cli.supervisor_token,
|
ENV_TOKEN: self.sys_plugins.cli.supervisor_token,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def image(self) -> str:
|
def image(self) -> str:
|
||||||
"""Return name of Supervisor DNS image."""
|
"""Return name of Supervisor DNS image."""
|
||||||
return self.sys_dns.image
|
return self.sys_plugins.dns.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -40,7 +40,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
|
|||||||
# Create & Run container
|
# Create & Run container
|
||||||
docker_container = self.sys_docker.run(
|
docker_container = self.sys_docker.run(
|
||||||
self.image,
|
self.image,
|
||||||
version=self.sys_dns.version,
|
version=self.sys_plugins.dns.version,
|
||||||
init=False,
|
init=False,
|
||||||
dns=False,
|
dns=False,
|
||||||
ipv4=self.sys_docker.network.dns,
|
ipv4=self.sys_docker.network.dns,
|
||||||
|
@ -72,11 +72,11 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
"bind": "/etc/pulse/client.conf",
|
"bind": "/etc/pulse/client.conf",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
str(self.sys_audio.path_extern_pulse): {
|
str(self.sys_plugins.audio.path_extern_pulse): {
|
||||||
"bind": "/run/audio",
|
"bind": "/run/audio",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
str(self.sys_audio.path_extern_asound): {
|
str(self.sys_plugins.audio.path_extern_asound): {
|
||||||
"bind": "/etc/asound.conf",
|
"bind": "/etc/asound.conf",
|
||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
},
|
},
|
||||||
|
59
supervisor/docker/multicast.py
Normal file
59
supervisor/docker/multicast.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""HA Cli docker object."""
|
||||||
|
from contextlib import suppress
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ..const import DOCKER_NETWORK_MASK, ENV_HASSIO_NETWORK, ENV_TIME
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import DockerAPIError
|
||||||
|
from .interface import DockerInterface
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MULTICAST_DOCKER_NAME: str = "hassio_multicast"
|
||||||
|
|
||||||
|
|
||||||
|
class DockerMulticast(DockerInterface, CoreSysAttributes):
|
||||||
|
"""Docker Supervisor wrapper for HA multicast."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image(self):
|
||||||
|
"""Return name of HA multicast image."""
|
||||||
|
return self.sys_plugins.multicast.image
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return name of Docker container."""
|
||||||
|
return MULTICAST_DOCKER_NAME
|
||||||
|
|
||||||
|
def _run(self) -> None:
|
||||||
|
"""Run Docker image.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
if self._is_running():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
with suppress(DockerAPIError):
|
||||||
|
self._stop()
|
||||||
|
|
||||||
|
# Create & Run container
|
||||||
|
docker_container = self.sys_docker.run(
|
||||||
|
self.image,
|
||||||
|
version=self.sys_plugins.multicast.version,
|
||||||
|
init=False,
|
||||||
|
name=self.name,
|
||||||
|
hostname=self.name.replace("_", "-"),
|
||||||
|
network_mode="host",
|
||||||
|
detach=True,
|
||||||
|
extra_hosts={"supervisor": self.sys_docker.network.supervisor},
|
||||||
|
environment={
|
||||||
|
ENV_TIME: self.sys_timezone,
|
||||||
|
ENV_HASSIO_NETWORK: str(DOCKER_NETWORK_MASK),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self._meta = docker_container.attrs
|
||||||
|
_LOGGER.info(
|
||||||
|
"Start Multicast %s with version %s - Host", self.image, self.version
|
||||||
|
)
|
@ -61,10 +61,21 @@ class CliError(HassioError):
|
|||||||
"""HA cli exception."""
|
"""HA cli exception."""
|
||||||
|
|
||||||
|
|
||||||
class CliUpdateError(HassOSError):
|
class CliUpdateError(CliError):
|
||||||
"""Error on update of a HA cli."""
|
"""Error on update of a HA cli."""
|
||||||
|
|
||||||
|
|
||||||
|
# Multicast
|
||||||
|
|
||||||
|
|
||||||
|
class MulticastError(HassioError):
|
||||||
|
"""Multicast exception."""
|
||||||
|
|
||||||
|
|
||||||
|
class MulticastUpdateError(MulticastError):
|
||||||
|
"""Error on update of a multicast."""
|
||||||
|
|
||||||
|
|
||||||
# DNS
|
# DNS
|
||||||
|
|
||||||
|
|
||||||
|
@ -636,7 +636,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
def write_pulse(self):
|
def write_pulse(self):
|
||||||
"""Write asound config to file and return True on success."""
|
"""Write asound config to file and return True on success."""
|
||||||
pulse_config = self.sys_audio.pulse_client(
|
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
input_profile=self.audio_input, output_profile=self.audio_output
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1 +1,76 @@
|
|||||||
"""Plugin for Supervisor backend."""
|
"""Plugin for Supervisor backend."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from .audio import Audio
|
||||||
|
from .cli import HaCli
|
||||||
|
from .dns import CoreDNS
|
||||||
|
from .multicast import Multicast
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginManager(CoreSysAttributes):
|
||||||
|
"""Manage supported function for plugins."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize plugin manager."""
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
|
||||||
|
self._cli: HaCli = HaCli(coresys)
|
||||||
|
self._dns: CoreDNS = CoreDNS(coresys)
|
||||||
|
self._audio: Audio = Audio(coresys)
|
||||||
|
self._multicast: Multicast = Multicast(coresys)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cli(self) -> HaCli:
|
||||||
|
"""Return cli handler."""
|
||||||
|
return self._cli
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dns(self) -> CoreDNS:
|
||||||
|
"""Return dns handler."""
|
||||||
|
return self._dns
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio(self) -> Audio:
|
||||||
|
"""Return audio handler."""
|
||||||
|
return self._audio
|
||||||
|
|
||||||
|
@property
|
||||||
|
def multicast(self) -> Multicast:
|
||||||
|
"""Return multicast handler."""
|
||||||
|
return self._multicast
|
||||||
|
|
||||||
|
async def load(self):
|
||||||
|
"""Load Supervisor plugins."""
|
||||||
|
await asyncio.wait(
|
||||||
|
[self.dns.load(), self.audio.load(), self.cli.load(), self.multicast.load()]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def repair(self):
|
||||||
|
"""Repair Supervisor plugins."""
|
||||||
|
await asyncio.wait(
|
||||||
|
[
|
||||||
|
self.dns.repair(),
|
||||||
|
self.audio.repair(),
|
||||||
|
self.cli.repair(),
|
||||||
|
self.multicast.repair(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def unload(self) -> None:
|
||||||
|
"""Unload Supervisor plugin."""
|
||||||
|
await asyncio.wait([self.dns.unload()])
|
||||||
|
|
||||||
|
async def shutdown(self) -> None:
|
||||||
|
"""Shutdown Supervisor plugin."""
|
||||||
|
await asyncio.wait(
|
||||||
|
[
|
||||||
|
self.dns.stop(),
|
||||||
|
self.audio.stop(),
|
||||||
|
self.cli.stop(),
|
||||||
|
self.multicast.stop(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""Home Assistant control object."""
|
"""Home Assistant audio plugin.
|
||||||
|
|
||||||
|
Code: https://github.com/home-assistant/plugin-audio
|
||||||
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
@ -14,12 +17,12 @@ from ..docker.audio import DockerAudio
|
|||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import AudioError, AudioUpdateError, DockerAPIError
|
from ..exceptions import AudioError, AudioUpdateError, DockerAPIError
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
from ..validate import SCHEMA_AUDIO_CONFIG
|
from .validate import SCHEMA_AUDIO_CONFIG
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PULSE_CLIENT_TMPL: Path = Path(__file__).parents[0].joinpath("data/pulse-client.tmpl")
|
PULSE_CLIENT_TMPL: Path = Path(__file__).parents[1].joinpath("data/pulse-client.tmpl")
|
||||||
ASOUND_TMPL: Path = Path(__file__).parents[0].joinpath("data/asound.tmpl")
|
ASOUND_TMPL: Path = Path(__file__).parents[1].joinpath("data/asound.tmpl")
|
||||||
|
|
||||||
|
|
||||||
class Audio(JsonConfig, CoreSysAttributes):
|
class Audio(JsonConfig, CoreSysAttributes):
|
||||||
@ -177,7 +180,6 @@ class Audio(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
"""Run CoreDNS."""
|
"""Run CoreDNS."""
|
||||||
# Start Instance
|
|
||||||
_LOGGER.info("Start Audio plugin")
|
_LOGGER.info("Start Audio plugin")
|
||||||
try:
|
try:
|
||||||
await self.instance.run()
|
await self.instance.run()
|
||||||
@ -185,6 +187,15 @@ class Audio(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.error("Can't start Audio plugin")
|
_LOGGER.error("Can't start Audio plugin")
|
||||||
raise AudioError() from None
|
raise AudioError() from None
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
"""Stop CoreDNS."""
|
||||||
|
_LOGGER.info("Stop Audio plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.stop()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't stop Audio plugin")
|
||||||
|
raise AudioError() from None
|
||||||
|
|
||||||
def logs(self) -> Awaitable[bytes]:
|
def logs(self) -> Awaitable[bytes]:
|
||||||
"""Get CoreDNS docker logs.
|
"""Get CoreDNS docker logs.
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""CLI support on supervisor."""
|
"""Home Assistant cli plugin.
|
||||||
|
|
||||||
|
Code: https://github.com/home-assistant/plugin-cli
|
||||||
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
@ -11,7 +14,7 @@ from ..docker.cli import DockerCli
|
|||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import CliError, CliUpdateError, DockerAPIError
|
from ..exceptions import CliError, CliUpdateError, DockerAPIError
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
from ..validate import SCHEMA_CLI_CONFIG
|
from .validate import SCHEMA_CLI_CONFIG
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -158,6 +161,15 @@ class HaCli(CoreSysAttributes, JsonConfig):
|
|||||||
_LOGGER.error("Can't start cli plugin")
|
_LOGGER.error("Can't start cli plugin")
|
||||||
raise CliError() from None
|
raise CliError() from None
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
"""Stop cli."""
|
||||||
|
_LOGGER.info("Stop cli plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.stop()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't stop cli plugin")
|
||||||
|
raise CliError() from None
|
||||||
|
|
||||||
async def stats(self) -> DockerStats:
|
async def stats(self) -> DockerStats:
|
||||||
"""Return stats of cli."""
|
"""Return stats of cli."""
|
||||||
try:
|
try:
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""Home Assistant control object."""
|
"""Home Assistant dns plugin.
|
||||||
|
|
||||||
|
Code: https://github.com/home-assistant/plugin-dns
|
||||||
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
@ -17,12 +20,13 @@ from ..docker.stats import DockerStats
|
|||||||
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerAPIError
|
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerAPIError
|
||||||
from ..misc.forwarder import DNSForward
|
from ..misc.forwarder import DNSForward
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
from ..validate import SCHEMA_DNS_CONFIG, dns_url
|
from .validate import SCHEMA_DNS_CONFIG
|
||||||
|
from ..validate import dns_url
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
COREDNS_TMPL: Path = Path(__file__).parents[0].joinpath("data/coredns.tmpl")
|
COREDNS_TMPL: Path = Path(__file__).parents[1].joinpath("data/coredns.tmpl")
|
||||||
RESOLV_TMPL: Path = Path(__file__).parents[0].joinpath("data/resolv.tmpl")
|
RESOLV_TMPL: Path = Path(__file__).parents[1].joinpath("data/resolv.tmpl")
|
||||||
HOST_RESOLV: Path = Path("/etc/resolv.conf")
|
HOST_RESOLV: Path = Path("/etc/resolv.conf")
|
||||||
|
|
||||||
|
|
||||||
@ -212,8 +216,12 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
async def restart(self) -> None:
|
async def restart(self) -> None:
|
||||||
"""Restart CoreDNS plugin."""
|
"""Restart CoreDNS plugin."""
|
||||||
self._write_corefile()
|
self._write_corefile()
|
||||||
with suppress(DockerAPIError):
|
_LOGGER.info("Restart CoreDNS plugin")
|
||||||
|
try:
|
||||||
await self.instance.restart()
|
await self.instance.restart()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't start CoreDNS plugin")
|
||||||
|
raise CoreDNSError()
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
"""Run CoreDNS."""
|
"""Run CoreDNS."""
|
||||||
@ -227,6 +235,15 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.error("Can't start CoreDNS plugin")
|
_LOGGER.error("Can't start CoreDNS plugin")
|
||||||
raise CoreDNSError() from None
|
raise CoreDNSError() from None
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
"""Stop CoreDNS."""
|
||||||
|
_LOGGER.info("Stop CoreDNS plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.stop()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't stop CoreDNS plugin")
|
||||||
|
raise CoreDNSError() from None
|
||||||
|
|
||||||
async def reset(self) -> None:
|
async def reset(self) -> None:
|
||||||
"""Reset DNS and hosts."""
|
"""Reset DNS and hosts."""
|
||||||
# Reset manually defined DNS
|
# Reset manually defined DNS
|
||||||
|
208
supervisor/plugins/multicast.py
Normal file
208
supervisor/plugins/multicast.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
"""Home Assistant multicast plugin.
|
||||||
|
|
||||||
|
Code: https://github.com/home-assistant/plugin-multicast
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
from contextlib import suppress
|
||||||
|
import logging
|
||||||
|
from typing import Awaitable, Optional
|
||||||
|
|
||||||
|
from ..const import ATTR_IMAGE, ATTR_VERSION, FILE_HASSIO_MULTICAST
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..docker.multicast import DockerMulticast
|
||||||
|
from ..docker.stats import DockerStats
|
||||||
|
from ..exceptions import DockerAPIError, MulticastError, MulticastUpdateError
|
||||||
|
from ..utils.json import JsonConfig
|
||||||
|
from .validate import SCHEMA_MULTICAST_CONFIG
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Multicast(JsonConfig, CoreSysAttributes):
|
||||||
|
"""Home Assistant core object for handle it."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize hass object."""
|
||||||
|
super().__init__(FILE_HASSIO_MULTICAST, SCHEMA_MULTICAST_CONFIG)
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
self.instance: DockerMulticast = DockerMulticast(coresys)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> Optional[str]:
|
||||||
|
"""Return current version of Multicast."""
|
||||||
|
return self._data.get(ATTR_VERSION)
|
||||||
|
|
||||||
|
@version.setter
|
||||||
|
def version(self, value: str) -> None:
|
||||||
|
"""Return current version of Multicast."""
|
||||||
|
self._data[ATTR_VERSION] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image(self) -> str:
|
||||||
|
"""Return current image of Multicast."""
|
||||||
|
if self._data.get(ATTR_IMAGE):
|
||||||
|
return self._data[ATTR_IMAGE]
|
||||||
|
return f"homeassistant/{self.sys_arch.supervisor}-hassio-multicast"
|
||||||
|
|
||||||
|
@image.setter
|
||||||
|
def image(self, value: str) -> None:
|
||||||
|
"""Return current image of Multicast."""
|
||||||
|
self._data[ATTR_IMAGE] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_version(self) -> Optional[str]:
|
||||||
|
"""Return latest version of Multicast."""
|
||||||
|
return self.sys_updater.version_multicast
|
||||||
|
|
||||||
|
@property
|
||||||
|
def in_progress(self) -> bool:
|
||||||
|
"""Return True if a task is in progress."""
|
||||||
|
return self.instance.in_progress
|
||||||
|
|
||||||
|
@property
|
||||||
|
def need_update(self) -> bool:
|
||||||
|
"""Return True if an update is available."""
|
||||||
|
return self.version != self.latest_version
|
||||||
|
|
||||||
|
async def load(self) -> None:
|
||||||
|
"""Load multicast setup."""
|
||||||
|
# Check Multicast state
|
||||||
|
try:
|
||||||
|
# Evaluate Version if we lost this information
|
||||||
|
if not self.version:
|
||||||
|
self.version = await self.instance.get_latest_version(key=int)
|
||||||
|
|
||||||
|
await self.instance.attach(tag=self.version)
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.info(
|
||||||
|
"No Multicast plugin Docker image %s found.", self.instance.image
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install Multicast plugin
|
||||||
|
with suppress(MulticastError):
|
||||||
|
await self.install()
|
||||||
|
else:
|
||||||
|
self.version = self.instance.version
|
||||||
|
self.image = self.instance.image
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
# Run Multicast plugin
|
||||||
|
with suppress(MulticastError):
|
||||||
|
if await self.instance.is_running():
|
||||||
|
await self.restart()
|
||||||
|
else:
|
||||||
|
await self.start()
|
||||||
|
|
||||||
|
async def install(self) -> None:
|
||||||
|
"""Install Multicast."""
|
||||||
|
_LOGGER.info("Setup Multicast plugin")
|
||||||
|
while True:
|
||||||
|
# read homeassistant tag and install it
|
||||||
|
if not self.latest_version:
|
||||||
|
await self.sys_updater.reload()
|
||||||
|
|
||||||
|
if self.latest_version:
|
||||||
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.install(
|
||||||
|
self.latest_version, image=self.sys_updater.image_multicast
|
||||||
|
)
|
||||||
|
break
|
||||||
|
_LOGGER.warning("Error on install Multicast plugin. Retry in 30sec")
|
||||||
|
await asyncio.sleep(30)
|
||||||
|
|
||||||
|
_LOGGER.info("Multicast plugin now installed")
|
||||||
|
self.version = self.instance.version
|
||||||
|
self.image = self.sys_updater.image_multicast
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
async def update(self, version: Optional[str] = None) -> None:
|
||||||
|
"""Update Multicast plugin."""
|
||||||
|
version = version or self.latest_version
|
||||||
|
old_image = self.image
|
||||||
|
|
||||||
|
if version == self.version:
|
||||||
|
_LOGGER.warning("Version %s is already installed for Multicast", version)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update
|
||||||
|
try:
|
||||||
|
await self.instance.update(version, image=self.sys_updater.image_multicast)
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Multicast update fails")
|
||||||
|
raise MulticastUpdateError() from None
|
||||||
|
else:
|
||||||
|
self.version = version
|
||||||
|
self.image = self.sys_updater.image_multicast
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.cleanup(old_image=old_image)
|
||||||
|
|
||||||
|
# Start Multicast plugin
|
||||||
|
await self.start()
|
||||||
|
|
||||||
|
async def restart(self) -> None:
|
||||||
|
"""Restart Multicast plugin."""
|
||||||
|
_LOGGER.info("Restart Multicast plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.restart()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't start Multicast plugin")
|
||||||
|
raise MulticastError()
|
||||||
|
|
||||||
|
async def start(self) -> None:
|
||||||
|
"""Run Multicast."""
|
||||||
|
_LOGGER.info("Start Multicast plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.run()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't start Multicast plugin")
|
||||||
|
raise MulticastError()
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
"""Stop Multicast."""
|
||||||
|
_LOGGER.info("Stop Multicast plugin")
|
||||||
|
try:
|
||||||
|
await self.instance.stop()
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Can't stop Multicast plugin")
|
||||||
|
raise MulticastError()
|
||||||
|
|
||||||
|
def logs(self) -> Awaitable[bytes]:
|
||||||
|
"""Get Multicast docker logs.
|
||||||
|
|
||||||
|
Return Coroutine.
|
||||||
|
"""
|
||||||
|
return self.instance.logs()
|
||||||
|
|
||||||
|
async def stats(self) -> DockerStats:
|
||||||
|
"""Return stats of Multicast."""
|
||||||
|
try:
|
||||||
|
return await self.instance.stats()
|
||||||
|
except DockerAPIError:
|
||||||
|
raise MulticastError() from None
|
||||||
|
|
||||||
|
def is_running(self) -> Awaitable[bool]:
|
||||||
|
"""Return True if Docker container is running.
|
||||||
|
|
||||||
|
Return a coroutine.
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
"""Repair Multicast plugin."""
|
||||||
|
if await self.instance.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info("Repair Multicast %s", self.version)
|
||||||
|
try:
|
||||||
|
await self.instance.install(self.version)
|
||||||
|
except DockerAPIError:
|
||||||
|
_LOGGER.error("Repairing of Multicast fails")
|
44
supervisor/plugins/validate.py
Normal file
44
supervisor/plugins/validate.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"""Validate functions."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import ATTR_ACCESS_TOKEN, ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION
|
||||||
|
from ..validate import dns_server_list, docker_image, token
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_DNS_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
|
vol.Optional(ATTR_SERVERS, default=list): dns_server_list,
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_AUDIO_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_CLI_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
|
vol.Optional(ATTR_ACCESS_TOKEN): token,
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_MULTICAST_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
@ -3,7 +3,13 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .coresys import CoreSysAttributes
|
from .coresys import CoreSysAttributes
|
||||||
from .exceptions import AudioError, CliError, CoreDNSError, HomeAssistantError
|
from .exceptions import (
|
||||||
|
AudioError,
|
||||||
|
CliError,
|
||||||
|
CoreDNSError,
|
||||||
|
HomeAssistantError,
|
||||||
|
MulticastError,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -14,6 +20,7 @@ RUN_UPDATE_ADDONS = 57600
|
|||||||
RUN_UPDATE_CLI = 28100
|
RUN_UPDATE_CLI = 28100
|
||||||
RUN_UPDATE_DNS = 30100
|
RUN_UPDATE_DNS = 30100
|
||||||
RUN_UPDATE_AUDIO = 30200
|
RUN_UPDATE_AUDIO = 30200
|
||||||
|
RUN_UPDATE_MULTICAST = 30300
|
||||||
|
|
||||||
RUN_RELOAD_ADDONS = 10800
|
RUN_RELOAD_ADDONS = 10800
|
||||||
RUN_RELOAD_SNAPSHOTS = 72000
|
RUN_RELOAD_SNAPSHOTS = 72000
|
||||||
@ -27,6 +34,7 @@ RUN_WATCHDOG_HOMEASSISTANT_API = 300
|
|||||||
RUN_WATCHDOG_DNS_DOCKER = 20
|
RUN_WATCHDOG_DNS_DOCKER = 20
|
||||||
RUN_WATCHDOG_AUDIO_DOCKER = 30
|
RUN_WATCHDOG_AUDIO_DOCKER = 30
|
||||||
RUN_WATCHDOG_CLI_DOCKER = 40
|
RUN_WATCHDOG_CLI_DOCKER = 40
|
||||||
|
RUN_WATCHDOG_MULTICAST_DOCKER = 50
|
||||||
|
|
||||||
|
|
||||||
class Tasks(CoreSysAttributes):
|
class Tasks(CoreSysAttributes):
|
||||||
@ -58,6 +66,11 @@ class Tasks(CoreSysAttributes):
|
|||||||
self.jobs.add(
|
self.jobs.add(
|
||||||
self.sys_scheduler.register_task(self._update_audio, RUN_UPDATE_AUDIO)
|
self.sys_scheduler.register_task(self._update_audio, RUN_UPDATE_AUDIO)
|
||||||
)
|
)
|
||||||
|
self.jobs.add(
|
||||||
|
self.sys_scheduler.register_task(
|
||||||
|
self._update_multicast, RUN_UPDATE_MULTICAST
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Reload
|
# Reload
|
||||||
self.jobs.add(
|
self.jobs.add(
|
||||||
@ -108,6 +121,11 @@ class Tasks(CoreSysAttributes):
|
|||||||
self._watchdog_cli_docker, RUN_WATCHDOG_CLI_DOCKER
|
self._watchdog_cli_docker, RUN_WATCHDOG_CLI_DOCKER
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.jobs.add(
|
||||||
|
self.sys_scheduler.register_task(
|
||||||
|
self._watchdog_multicast_docker, RUN_WATCHDOG_MULTICAST_DOCKER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.info("All core tasks are scheduled")
|
_LOGGER.info("All core tasks are scheduled")
|
||||||
|
|
||||||
@ -209,66 +227,92 @@ class Tasks(CoreSysAttributes):
|
|||||||
|
|
||||||
async def _update_cli(self):
|
async def _update_cli(self):
|
||||||
"""Check and run update of cli."""
|
"""Check and run update of cli."""
|
||||||
if not self.sys_cli.need_update:
|
if not self.sys_plugins.cli.need_update:
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info("Found new cli version")
|
_LOGGER.info("Found new cli version")
|
||||||
await self.sys_cli.update()
|
await self.sys_plugins.cli.update()
|
||||||
|
|
||||||
async def _update_dns(self):
|
async def _update_dns(self):
|
||||||
"""Check and run update of CoreDNS plugin."""
|
"""Check and run update of CoreDNS plugin."""
|
||||||
if not self.sys_dns.need_update:
|
if not self.sys_plugins.dns.need_update:
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info("Found new CoreDNS plugin version")
|
_LOGGER.info("Found new CoreDNS plugin version")
|
||||||
await self.sys_dns.update()
|
await self.sys_plugins.dns.update()
|
||||||
|
|
||||||
async def _update_audio(self):
|
async def _update_audio(self):
|
||||||
"""Check and run update of PulseAudio plugin."""
|
"""Check and run update of PulseAudio plugin."""
|
||||||
if not self.sys_audio.need_update:
|
if not self.sys_plugins.audio.need_update:
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info("Found new PulseAudio plugin version")
|
_LOGGER.info("Found new PulseAudio plugin version")
|
||||||
await self.sys_audio.update()
|
await self.sys_plugins.audio.update()
|
||||||
|
|
||||||
|
async def _update_multicast(self):
|
||||||
|
"""Check and run update of multicast."""
|
||||||
|
if not self.sys_plugins.multicast.need_update:
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info("Found new Multicast version")
|
||||||
|
await self.sys_plugins.multicast.update()
|
||||||
|
|
||||||
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() or self.sys_dns.in_progress:
|
if await self.sys_plugins.dns.is_running() or self.sys_plugins.dns.in_progress:
|
||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
||||||
|
|
||||||
# Reset of fails
|
# Reset of fails
|
||||||
if await self.sys_dns.is_fails():
|
if await self.sys_plugins.dns.is_fails():
|
||||||
_LOGGER.error("CoreDNS plugin is in fails state / Reset config")
|
_LOGGER.error("CoreDNS plugin is in fails state / Reset config")
|
||||||
await self.sys_dns.reset()
|
await self.sys_plugins.dns.reset()
|
||||||
await self.sys_dns.loop_detection()
|
await self.sys_plugins.dns.loop_detection()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.sys_dns.start()
|
await self.sys_plugins.dns.start()
|
||||||
except CoreDNSError:
|
except CoreDNSError:
|
||||||
_LOGGER.error("Watchdog CoreDNS reanimation fails!")
|
_LOGGER.error("Watchdog CoreDNS reanimation fails!")
|
||||||
|
|
||||||
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() or self.sys_audio.in_progress:
|
if (
|
||||||
|
await self.sys_plugins.audio.is_running()
|
||||||
|
or self.sys_plugins.audio.in_progress
|
||||||
|
):
|
||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with PulseAudio plugin!")
|
_LOGGER.warning("Watchdog found a problem with PulseAudio plugin!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.sys_audio.start()
|
await self.sys_plugins.audio.start()
|
||||||
except AudioError:
|
except AudioError:
|
||||||
_LOGGER.error("Watchdog PulseAudio reanimation fails!")
|
_LOGGER.error("Watchdog PulseAudio reanimation fails!")
|
||||||
|
|
||||||
async def _watchdog_cli_docker(self):
|
async def _watchdog_cli_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 cli plugin is active
|
# if cli plugin is active
|
||||||
if await self.sys_cli.is_running() or self.sys_cli.in_progress:
|
if await self.sys_plugins.cli.is_running() or self.sys_plugins.cli.in_progress:
|
||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with cli plugin!")
|
_LOGGER.warning("Watchdog found a problem with cli plugin!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.sys_cli.start()
|
await self.sys_plugins.cli.start()
|
||||||
except CliError:
|
except CliError:
|
||||||
_LOGGER.error("Watchdog cli reanimation fails!")
|
_LOGGER.error("Watchdog cli reanimation fails!")
|
||||||
|
|
||||||
|
async def _watchdog_multicast_docker(self):
|
||||||
|
"""Check running state of Docker and start if they is close."""
|
||||||
|
# if multicast plugin is active
|
||||||
|
if (
|
||||||
|
await self.sys_plugins.multicast.is_running()
|
||||||
|
or self.sys_plugins.multicast.in_progress
|
||||||
|
):
|
||||||
|
return
|
||||||
|
_LOGGER.warning("Watchdog found a problem with Multicast plugin!")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.sys_plugins.multicast.start()
|
||||||
|
except MulticastError:
|
||||||
|
_LOGGER.error("Watchdog Multicast reanimation fails!")
|
||||||
|
@ -13,10 +13,11 @@ from .const import (
|
|||||||
ATTR_CHANNEL,
|
ATTR_CHANNEL,
|
||||||
ATTR_CLI,
|
ATTR_CLI,
|
||||||
ATTR_DNS,
|
ATTR_DNS,
|
||||||
ATTR_SUPERVISOR,
|
|
||||||
ATTR_HASSOS,
|
ATTR_HASSOS,
|
||||||
ATTR_HOMEASSISTANT,
|
ATTR_HOMEASSISTANT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
|
ATTR_MULTICAST,
|
||||||
|
ATTR_SUPERVISOR,
|
||||||
FILE_HASSIO_UPDATER,
|
FILE_HASSIO_UPDATER,
|
||||||
URL_HASSIO_VERSION,
|
URL_HASSIO_VERSION,
|
||||||
UpdateChannels,
|
UpdateChannels,
|
||||||
@ -78,6 +79,11 @@ class Updater(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return latest version of Audio."""
|
"""Return latest version of Audio."""
|
||||||
return self._data.get(ATTR_AUDIO)
|
return self._data.get(ATTR_AUDIO)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version_multicast(self) -> Optional[str]:
|
||||||
|
"""Return latest version of Multicast."""
|
||||||
|
return self._data.get(ATTR_MULTICAST)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_homeassistant(self) -> Optional[str]:
|
def image_homeassistant(self) -> Optional[str]:
|
||||||
"""Return latest version of Home Assistant."""
|
"""Return latest version of Home Assistant."""
|
||||||
@ -123,6 +129,15 @@ class Updater(JsonConfig, CoreSysAttributes):
|
|||||||
.format(arch=self.sys_arch.supervisor)
|
.format(arch=self.sys_arch.supervisor)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_multicast(self) -> Optional[str]:
|
||||||
|
"""Return latest version of Multicast."""
|
||||||
|
return (
|
||||||
|
self._data[ATTR_IMAGE]
|
||||||
|
.get(ATTR_MULTICAST, "")
|
||||||
|
.format(arch=self.sys_arch.supervisor)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channel(self) -> UpdateChannels:
|
def channel(self) -> UpdateChannels:
|
||||||
"""Return upstream channel of Supervisor instance."""
|
"""Return upstream channel of Supervisor instance."""
|
||||||
@ -171,10 +186,11 @@ class Updater(JsonConfig, CoreSysAttributes):
|
|||||||
if self.sys_hassos.board:
|
if self.sys_hassos.board:
|
||||||
self._data[ATTR_HASSOS] = data["hassos"][self.sys_hassos.board]
|
self._data[ATTR_HASSOS] = data["hassos"][self.sys_hassos.board]
|
||||||
|
|
||||||
# Update Home Assistant services
|
# Update Home Assistant plugins
|
||||||
self._data[ATTR_CLI] = data["cli"]
|
self._data[ATTR_CLI] = data["cli"]
|
||||||
self._data[ATTR_DNS] = data["dns"]
|
self._data[ATTR_DNS] = data["dns"]
|
||||||
self._data[ATTR_AUDIO] = data["audio"]
|
self._data[ATTR_AUDIO] = data["audio"]
|
||||||
|
self._data[ATTR_MULTICAST] = data["multicast"]
|
||||||
|
|
||||||
# Update images for that versions
|
# Update images for that versions
|
||||||
self._data[ATTR_IMAGE][ATTR_HOMEASSISTANT] = data["image"]["core"]
|
self._data[ATTR_IMAGE][ATTR_HOMEASSISTANT] = data["image"]["core"]
|
||||||
@ -182,6 +198,7 @@ class Updater(JsonConfig, CoreSysAttributes):
|
|||||||
self._data[ATTR_IMAGE][ATTR_AUDIO] = data["image"]["audio"]
|
self._data[ATTR_IMAGE][ATTR_AUDIO] = data["image"]["audio"]
|
||||||
self._data[ATTR_IMAGE][ATTR_CLI] = data["image"]["cli"]
|
self._data[ATTR_IMAGE][ATTR_CLI] = data["image"]["cli"]
|
||||||
self._data[ATTR_IMAGE][ATTR_DNS] = data["image"]["dns"]
|
self._data[ATTR_IMAGE][ATTR_DNS] = data["image"]["dns"]
|
||||||
|
self._data[ATTR_IMAGE][ATTR_MULTICAST] = data["image"]["multicast"]
|
||||||
|
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
_LOGGER.warning("Can't process version data: %s", err)
|
_LOGGER.warning("Can't process version data: %s", err)
|
||||||
|
@ -17,18 +17,18 @@ from .const import (
|
|||||||
ATTR_DEBUG,
|
ATTR_DEBUG,
|
||||||
ATTR_DEBUG_BLOCK,
|
ATTR_DEBUG_BLOCK,
|
||||||
ATTR_DNS,
|
ATTR_DNS,
|
||||||
ATTR_SUPERVISOR,
|
|
||||||
ATTR_HASSOS,
|
ATTR_HASSOS,
|
||||||
ATTR_HOMEASSISTANT,
|
ATTR_HOMEASSISTANT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
ATTR_LAST_BOOT,
|
ATTR_LAST_BOOT,
|
||||||
ATTR_LOGGING,
|
ATTR_LOGGING,
|
||||||
|
ATTR_MULTICAST,
|
||||||
ATTR_PORT,
|
ATTR_PORT,
|
||||||
ATTR_PORTS,
|
ATTR_PORTS,
|
||||||
ATTR_REFRESH_TOKEN,
|
ATTR_REFRESH_TOKEN,
|
||||||
ATTR_SERVERS,
|
|
||||||
ATTR_SESSION,
|
ATTR_SESSION,
|
||||||
ATTR_SSL,
|
ATTR_SSL,
|
||||||
|
ATTR_SUPERVISOR,
|
||||||
ATTR_TIMEZONE,
|
ATTR_TIMEZONE,
|
||||||
ATTR_UUID,
|
ATTR_UUID,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
@ -129,6 +129,7 @@ SCHEMA_UPDATER_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_CLI): vol.Coerce(str),
|
vol.Optional(ATTR_CLI): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_DNS): vol.Coerce(str),
|
vol.Optional(ATTR_DNS): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_AUDIO): vol.Coerce(str),
|
vol.Optional(ATTR_AUDIO): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_MULTICAST): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_IMAGE, default=dict): vol.Schema(
|
vol.Optional(ATTR_IMAGE, default=dict): vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): docker_image,
|
vol.Optional(ATTR_HOMEASSISTANT): docker_image,
|
||||||
@ -136,6 +137,7 @@ SCHEMA_UPDATER_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_CLI): docker_image,
|
vol.Optional(ATTR_CLI): docker_image,
|
||||||
vol.Optional(ATTR_DNS): docker_image,
|
vol.Optional(ATTR_DNS): docker_image,
|
||||||
vol.Optional(ATTR_AUDIO): docker_image,
|
vol.Optional(ATTR_AUDIO): docker_image,
|
||||||
|
vol.Optional(ATTR_MULTICAST): docker_image,
|
||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
),
|
),
|
||||||
@ -176,32 +178,3 @@ SCHEMA_INGRESS_CONFIG = vol.Schema(
|
|||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_DNS_CONFIG = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
|
||||||
vol.Optional(ATTR_SERVERS, default=list): dns_server_list,
|
|
||||||
},
|
|
||||||
extra=vol.REMOVE_EXTRA,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_AUDIO_CONFIG = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
|
||||||
},
|
|
||||||
extra=vol.REMOVE_EXTRA,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_CLI_CONFIG = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
|
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): token,
|
|
||||||
},
|
|
||||||
extra=vol.REMOVE_EXTRA,
|
|
||||||
)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user