mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-25 18:16:32 +00:00
commit
3889504292
@ -10,7 +10,7 @@ gitpython==3.1.2
|
|||||||
jinja2==2.11.2
|
jinja2==2.11.2
|
||||||
packaging==20.4
|
packaging==20.4
|
||||||
ptvsd==4.3.2
|
ptvsd==4.3.2
|
||||||
pulsectl==20.5.0
|
pulsectl==20.5.1
|
||||||
pytz==2020.1
|
pytz==2020.1
|
||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
ruamel.yaml==0.15.100
|
ruamel.yaml==0.15.100
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
flake8==3.8.1
|
flake8==3.8.2
|
||||||
pylint==2.5.2
|
pylint==2.5.2
|
||||||
pytest==5.4.2
|
pytest==5.4.2
|
||||||
pytest-timeout==1.3.4
|
pytest-timeout==1.3.4
|
||||||
|
@ -37,7 +37,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def all(self) -> List[AnyAddon]:
|
def all(self) -> List[AnyAddon]:
|
||||||
"""Return a list of all add-ons."""
|
"""Return a list of all add-ons."""
|
||||||
addons = {**self.store, **self.local}
|
addons: Dict[str, AnyAddon] = {**self.store, **self.local}
|
||||||
return list(addons.values())
|
return list(addons.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -142,7 +142,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
_LOGGER.warning("Add-on %s is not installed", slug)
|
_LOGGER.warning("Add-on %s is not installed", slug)
|
||||||
return
|
return
|
||||||
addon = self.local.get(slug)
|
addon = self.local[slug]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await addon.instance.remove()
|
await addon.instance.remove()
|
||||||
@ -191,12 +191,12 @@ class AddonManager(CoreSysAttributes):
|
|||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
_LOGGER.error("Add-on %s is not installed", slug)
|
_LOGGER.error("Add-on %s is not installed", slug)
|
||||||
raise AddonsError()
|
raise AddonsError()
|
||||||
addon = self.local.get(slug)
|
addon = self.local[slug]
|
||||||
|
|
||||||
if addon.is_detached:
|
if addon.is_detached:
|
||||||
_LOGGER.error("Add-on %s is not available inside store", slug)
|
_LOGGER.error("Add-on %s is not available inside store", slug)
|
||||||
raise AddonsError()
|
raise AddonsError()
|
||||||
store = self.store.get(slug)
|
store = self.store[slug]
|
||||||
|
|
||||||
if addon.version == store.version:
|
if addon.version == store.version:
|
||||||
_LOGGER.warning("No update available for add-on %s", slug)
|
_LOGGER.warning("No update available for add-on %s", slug)
|
||||||
@ -233,12 +233,12 @@ class AddonManager(CoreSysAttributes):
|
|||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
_LOGGER.error("Add-on %s is not installed", slug)
|
_LOGGER.error("Add-on %s is not installed", slug)
|
||||||
raise AddonsError()
|
raise AddonsError()
|
||||||
addon = self.local.get(slug)
|
addon = self.local[slug]
|
||||||
|
|
||||||
if addon.is_detached:
|
if addon.is_detached:
|
||||||
_LOGGER.error("Add-on %s is not available inside store", slug)
|
_LOGGER.error("Add-on %s is not available inside store", slug)
|
||||||
raise AddonsError()
|
raise AddonsError()
|
||||||
store = self.store.get(slug)
|
store = self.store[slug]
|
||||||
|
|
||||||
# Check if a rebuild is possible now
|
# Check if a rebuild is possible now
|
||||||
if addon.version != store.version:
|
if addon.version != store.version:
|
||||||
|
@ -215,7 +215,8 @@ class AddonModel(CoreSysAttributes):
|
|||||||
services = {}
|
services = {}
|
||||||
for data in services_list:
|
for data in services_list:
|
||||||
service = RE_SERVICE.match(data)
|
service = RE_SERVICE.match(data)
|
||||||
services[service.group("service")] = service.group("rights")
|
if service:
|
||||||
|
services[service.group("service")] = service.group("rights")
|
||||||
|
|
||||||
return services
|
return services
|
||||||
|
|
||||||
@ -464,6 +465,8 @@ class AddonModel(CoreSysAttributes):
|
|||||||
volumes = {}
|
volumes = {}
|
||||||
for volume in self.data[ATTR_MAP]:
|
for volume in self.data[ATTR_MAP]:
|
||||||
result = RE_VOLUME.match(volume)
|
result = RE_VOLUME.match(volume)
|
||||||
|
if not result:
|
||||||
|
continue
|
||||||
volumes[result.group(1)] = result.group(2) or "ro"
|
volumes[result.group(1)] = result.group(2) or "ro"
|
||||||
|
|
||||||
return volumes
|
return volumes
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Union
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -385,6 +385,9 @@ def _single_validate(coresys: CoreSys, typ: str, value: Any, key: str):
|
|||||||
# parse extend data from type
|
# parse extend data from type
|
||||||
match = RE_SCHEMA_ELEMENT.match(typ)
|
match = RE_SCHEMA_ELEMENT.match(typ)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
raise vol.Invalid(f"Unknown type {typ}")
|
||||||
|
|
||||||
# prepare range
|
# prepare range
|
||||||
range_args = {}
|
range_args = {}
|
||||||
for group_name in _SCHEMA_LENGTH_PARTS:
|
for group_name in _SCHEMA_LENGTH_PARTS:
|
||||||
@ -462,7 +465,7 @@ def _check_missing_options(origin, exists, root):
|
|||||||
|
|
||||||
def schema_ui_options(raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
def schema_ui_options(raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
"""Generate UI schema."""
|
"""Generate UI schema."""
|
||||||
ui_schema = []
|
ui_schema: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
# read options
|
# read options
|
||||||
for key, value in raw_schema.items():
|
for key, value in raw_schema.items():
|
||||||
@ -483,7 +486,7 @@ def _single_ui_option(
|
|||||||
ui_schema: List[Dict[str, Any]], value: str, key: str, multiple: bool = False
|
ui_schema: List[Dict[str, Any]], value: str, key: str, multiple: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Validate a single element."""
|
"""Validate a single element."""
|
||||||
ui_node = {"name": key}
|
ui_node: Dict[str, Union[str, bool, float, List[str]]] = {"name": key}
|
||||||
|
|
||||||
# If multiple
|
# If multiple
|
||||||
if multiple:
|
if multiple:
|
||||||
@ -491,6 +494,8 @@ def _single_ui_option(
|
|||||||
|
|
||||||
# Parse extend data from type
|
# Parse extend data from type
|
||||||
match = RE_SCHEMA_ELEMENT.match(value)
|
match = RE_SCHEMA_ELEMENT.match(value)
|
||||||
|
if not match:
|
||||||
|
return
|
||||||
|
|
||||||
# Prepare range
|
# Prepare range
|
||||||
for group_name in _SCHEMA_LENGTH_PARTS:
|
for group_name in _SCHEMA_LENGTH_PARTS:
|
||||||
|
@ -33,7 +33,7 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..validate import docker_image, network_port, complex_version
|
from ..validate import complex_version, docker_image, network_port
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
@ -43,7 +43,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..utils.validate import validate_timezone
|
from ..utils.validate import validate_timezone
|
||||||
from ..validate import repositories, wait_boot, simple_version
|
from ..validate import repositories, simple_version, wait_boot
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
@ -27,15 +27,16 @@ from .discovery import Discovery
|
|||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .hwmon import HwMonitor
|
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
|
from .misc.hwmon import HwMonitor
|
||||||
|
from .misc.scheduler import Scheduler
|
||||||
|
from .misc.secrets import SecretsManager
|
||||||
|
from .misc.tasks import Tasks
|
||||||
from .plugins import PluginManager
|
from .plugins import PluginManager
|
||||||
from .secrets import SecretsManager
|
|
||||||
from .services import ServiceManager
|
from .services import ServiceManager
|
||||||
from .snapshots import SnapshotManager
|
from .snapshots import SnapshotManager
|
||||||
from .store import StoreManager
|
from .store import StoreManager
|
||||||
from .supervisor import Supervisor
|
from .supervisor import Supervisor
|
||||||
from .tasks import Tasks
|
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .utils.dt import fetch_timezone
|
from .utils.dt import fetch_timezone
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
MACHINE_ID = Path("/etc/machine-id")
|
MACHINE_ID = Path("/etc/machine-id")
|
||||||
|
|
||||||
|
|
||||||
async def initialize_coresys():
|
async def initialize_coresys() -> None:
|
||||||
"""Initialize supervisor coresys/objects."""
|
"""Initialize supervisor coresys/objects."""
|
||||||
coresys = CoreSys()
|
coresys = CoreSys()
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ 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.scheduler = Scheduler(coresys)
|
||||||
|
|
||||||
# bootstrap config
|
# bootstrap config
|
||||||
initialize_system_data(coresys)
|
initialize_system_data(coresys)
|
||||||
@ -92,7 +94,7 @@ async def initialize_coresys():
|
|||||||
return coresys
|
return coresys
|
||||||
|
|
||||||
|
|
||||||
def initialize_system_data(coresys: CoreSys):
|
def initialize_system_data(coresys: CoreSys) -> None:
|
||||||
"""Set up the default configuration and create folders."""
|
"""Set up the default configuration and create folders."""
|
||||||
config = coresys.config
|
config = coresys.config
|
||||||
|
|
||||||
@ -168,7 +170,7 @@ def initialize_system_data(coresys: CoreSys):
|
|||||||
coresys.config.debug = True
|
coresys.config.debug = True
|
||||||
|
|
||||||
|
|
||||||
def migrate_system_env(coresys: CoreSys):
|
def migrate_system_env(coresys: CoreSys) -> None:
|
||||||
"""Cleanup some stuff after update."""
|
"""Cleanup some stuff after update."""
|
||||||
config = coresys.config
|
config = coresys.config
|
||||||
|
|
||||||
@ -181,7 +183,7 @@ def migrate_system_env(coresys: CoreSys):
|
|||||||
_LOGGER.warning("Can't cleanup old Add-on build directory")
|
_LOGGER.warning("Can't cleanup old Add-on build directory")
|
||||||
|
|
||||||
|
|
||||||
def initialize_logging():
|
def initialize_logging() -> None:
|
||||||
"""Initialize the logging."""
|
"""Initialize the logging."""
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||||
@ -237,7 +239,7 @@ def check_environment() -> None:
|
|||||||
_LOGGER.critical("Can't find gdbus!")
|
_LOGGER.critical("Can't find gdbus!")
|
||||||
|
|
||||||
|
|
||||||
def reg_signal(loop):
|
def reg_signal(loop) -> None:
|
||||||
"""Register SIGTERM and SIGKILL to stop system."""
|
"""Register SIGTERM and SIGKILL to stop system."""
|
||||||
try:
|
try:
|
||||||
loop.add_signal_handler(signal.SIGTERM, lambda: loop.call_soon(loop.stop))
|
loop.add_signal_handler(signal.SIGTERM, lambda: loop.call_soon(loop.stop))
|
||||||
|
@ -3,7 +3,7 @@ from datetime import datetime
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ADDONS_CUSTOM_LIST,
|
ATTR_ADDONS_CUSTOM_LIST,
|
||||||
@ -52,12 +52,12 @@ class CoreConfig(JsonConfig):
|
|||||||
super().__init__(FILE_HASSIO_CONFIG, SCHEMA_SUPERVISOR_CONFIG)
|
super().__init__(FILE_HASSIO_CONFIG, SCHEMA_SUPERVISOR_CONFIG)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timezone(self):
|
def timezone(self) -> str:
|
||||||
"""Return system timezone."""
|
"""Return system timezone."""
|
||||||
return self._data[ATTR_TIMEZONE]
|
return self._data[ATTR_TIMEZONE]
|
||||||
|
|
||||||
@timezone.setter
|
@timezone.setter
|
||||||
def timezone(self, value):
|
def timezone(self, value: str) -> None:
|
||||||
"""Set system timezone."""
|
"""Set system timezone."""
|
||||||
self._data[ATTR_TIMEZONE] = value
|
self._data[ATTR_TIMEZONE] = value
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ class CoreConfig(JsonConfig):
|
|||||||
return self._data.get(ATTR_VERSION)
|
return self._data.get(ATTR_VERSION)
|
||||||
|
|
||||||
@version.setter
|
@version.setter
|
||||||
def version(self, value: str):
|
def version(self, value: str) -> None:
|
||||||
"""Set config version."""
|
"""Set config version."""
|
||||||
self._data[ATTR_VERSION] = value
|
self._data[ATTR_VERSION] = value
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class CoreConfig(JsonConfig):
|
|||||||
return self._data[ATTR_WAIT_BOOT]
|
return self._data[ATTR_WAIT_BOOT]
|
||||||
|
|
||||||
@wait_boot.setter
|
@wait_boot.setter
|
||||||
def wait_boot(self, value: int):
|
def wait_boot(self, value: int) -> None:
|
||||||
"""Set wait boot time."""
|
"""Set wait boot time."""
|
||||||
self._data[ATTR_WAIT_BOOT] = value
|
self._data[ATTR_WAIT_BOOT] = value
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ class CoreConfig(JsonConfig):
|
|||||||
return self._data[ATTR_DEBUG]
|
return self._data[ATTR_DEBUG]
|
||||||
|
|
||||||
@debug.setter
|
@debug.setter
|
||||||
def debug(self, value: bool):
|
def debug(self, value: bool) -> None:
|
||||||
"""Set debug mode."""
|
"""Set debug mode."""
|
||||||
self._data[ATTR_DEBUG] = value
|
self._data[ATTR_DEBUG] = value
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ class CoreConfig(JsonConfig):
|
|||||||
return self._data[ATTR_DEBUG_BLOCK]
|
return self._data[ATTR_DEBUG_BLOCK]
|
||||||
|
|
||||||
@debug_block.setter
|
@debug_block.setter
|
||||||
def debug_block(self, value: bool):
|
def debug_block(self, value: bool) -> None:
|
||||||
"""Set debug wait mode."""
|
"""Set debug wait mode."""
|
||||||
self._data[ATTR_DEBUG_BLOCK] = value
|
self._data[ATTR_DEBUG_BLOCK] = value
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ class CoreConfig(JsonConfig):
|
|||||||
return self._data[ATTR_LOGGING]
|
return self._data[ATTR_LOGGING]
|
||||||
|
|
||||||
@logging.setter
|
@logging.setter
|
||||||
def logging(self, value: LogLevel):
|
def logging(self, value: LogLevel) -> None:
|
||||||
"""Set system log level."""
|
"""Set system log level."""
|
||||||
self._data[ATTR_LOGGING] = value
|
self._data[ATTR_LOGGING] = value
|
||||||
self.modify_log_level()
|
self.modify_log_level()
|
||||||
@ -118,7 +118,7 @@ class CoreConfig(JsonConfig):
|
|||||||
logging.getLogger("supervisor").setLevel(lvl)
|
logging.getLogger("supervisor").setLevel(lvl)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_boot(self):
|
def last_boot(self) -> datetime:
|
||||||
"""Return last boot datetime."""
|
"""Return last boot datetime."""
|
||||||
boot_str = self._data.get(ATTR_LAST_BOOT, DEFAULT_BOOT_TIME)
|
boot_str = self._data.get(ATTR_LAST_BOOT, DEFAULT_BOOT_TIME)
|
||||||
|
|
||||||
@ -128,138 +128,138 @@ class CoreConfig(JsonConfig):
|
|||||||
return boot_time
|
return boot_time
|
||||||
|
|
||||||
@last_boot.setter
|
@last_boot.setter
|
||||||
def last_boot(self, value):
|
def last_boot(self, value: datetime) -> None:
|
||||||
"""Set last boot datetime."""
|
"""Set last boot datetime."""
|
||||||
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_supervisor(self):
|
def path_supervisor(self) -> Path:
|
||||||
"""Return Supervisor data path."""
|
"""Return Supervisor data path."""
|
||||||
return SUPERVISOR_DATA
|
return SUPERVISOR_DATA
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_supervisor(self):
|
def path_extern_supervisor(self) -> PurePath:
|
||||||
"""Return Supervisor data path external for Docker."""
|
"""Return Supervisor data path external for Docker."""
|
||||||
return PurePath(os.environ[ENV_SUPERVISOR_SHARE])
|
return PurePath(os.environ[ENV_SUPERVISOR_SHARE])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_homeassistant(self):
|
def path_extern_homeassistant(self) -> str:
|
||||||
"""Return config path external for Docker."""
|
"""Return config path external for Docker."""
|
||||||
return str(PurePath(self.path_extern_supervisor, HOMEASSISTANT_CONFIG))
|
return str(PurePath(self.path_extern_supervisor, HOMEASSISTANT_CONFIG))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_homeassistant(self):
|
def path_homeassistant(self) -> Path:
|
||||||
"""Return config path inside supervisor."""
|
"""Return config path inside supervisor."""
|
||||||
return Path(SUPERVISOR_DATA, HOMEASSISTANT_CONFIG)
|
return Path(SUPERVISOR_DATA, HOMEASSISTANT_CONFIG)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_ssl(self):
|
def path_extern_ssl(self) -> str:
|
||||||
"""Return SSL path external for Docker."""
|
"""Return SSL path external for Docker."""
|
||||||
return str(PurePath(self.path_extern_supervisor, HASSIO_SSL))
|
return str(PurePath(self.path_extern_supervisor, HASSIO_SSL))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_ssl(self):
|
def path_ssl(self) -> Path:
|
||||||
"""Return SSL path inside supervisor."""
|
"""Return SSL path inside supervisor."""
|
||||||
return Path(SUPERVISOR_DATA, HASSIO_SSL)
|
return Path(SUPERVISOR_DATA, HASSIO_SSL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_addons_core(self):
|
def path_addons_core(self) -> Path:
|
||||||
"""Return git path for core Add-ons."""
|
"""Return git path for core Add-ons."""
|
||||||
return Path(SUPERVISOR_DATA, ADDONS_CORE)
|
return Path(SUPERVISOR_DATA, ADDONS_CORE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_addons_git(self):
|
def path_addons_git(self) -> Path:
|
||||||
"""Return path for Git Add-on."""
|
"""Return path for Git Add-on."""
|
||||||
return Path(SUPERVISOR_DATA, ADDONS_GIT)
|
return Path(SUPERVISOR_DATA, ADDONS_GIT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_addons_local(self):
|
def path_addons_local(self) -> Path:
|
||||||
"""Return path for custom Add-ons."""
|
"""Return path for custom Add-ons."""
|
||||||
return Path(SUPERVISOR_DATA, ADDONS_LOCAL)
|
return Path(SUPERVISOR_DATA, ADDONS_LOCAL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_addons_local(self):
|
def path_extern_addons_local(self) -> PurePath:
|
||||||
"""Return path for custom Add-ons."""
|
"""Return path for custom Add-ons."""
|
||||||
return PurePath(self.path_extern_supervisor, ADDONS_LOCAL)
|
return PurePath(self.path_extern_supervisor, ADDONS_LOCAL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_addons_data(self):
|
def path_addons_data(self) -> Path:
|
||||||
"""Return root Add-on data folder."""
|
"""Return root Add-on data folder."""
|
||||||
return Path(SUPERVISOR_DATA, ADDONS_DATA)
|
return Path(SUPERVISOR_DATA, ADDONS_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_addons_data(self):
|
def path_extern_addons_data(self) -> PurePath:
|
||||||
"""Return root add-on data folder external for Docker."""
|
"""Return root add-on data folder external for Docker."""
|
||||||
return PurePath(self.path_extern_supervisor, ADDONS_DATA)
|
return PurePath(self.path_extern_supervisor, ADDONS_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_audio(self):
|
def path_audio(self) -> Path:
|
||||||
"""Return root audio data folder."""
|
"""Return root audio data folder."""
|
||||||
return Path(SUPERVISOR_DATA, AUDIO_DATA)
|
return Path(SUPERVISOR_DATA, AUDIO_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_audio(self):
|
def path_extern_audio(self) -> PurePath:
|
||||||
"""Return root audio data folder external for Docker."""
|
"""Return root audio data folder external for Docker."""
|
||||||
return PurePath(self.path_extern_supervisor, AUDIO_DATA)
|
return PurePath(self.path_extern_supervisor, AUDIO_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_tmp(self):
|
def path_tmp(self) -> Path:
|
||||||
"""Return Supervisor temp folder."""
|
"""Return Supervisor temp folder."""
|
||||||
return Path(SUPERVISOR_DATA, TMP_DATA)
|
return Path(SUPERVISOR_DATA, TMP_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_tmp(self):
|
def path_extern_tmp(self) -> PurePath:
|
||||||
"""Return Supervisor temp folder for Docker."""
|
"""Return Supervisor temp folder for Docker."""
|
||||||
return PurePath(self.path_extern_supervisor, TMP_DATA)
|
return PurePath(self.path_extern_supervisor, TMP_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_backup(self):
|
def path_backup(self) -> Path:
|
||||||
"""Return root backup data folder."""
|
"""Return root backup data folder."""
|
||||||
return Path(SUPERVISOR_DATA, BACKUP_DATA)
|
return Path(SUPERVISOR_DATA, BACKUP_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_backup(self):
|
def path_extern_backup(self) -> PurePath:
|
||||||
"""Return root backup data folder external for Docker."""
|
"""Return root backup data folder external for Docker."""
|
||||||
return PurePath(self.path_extern_supervisor, BACKUP_DATA)
|
return PurePath(self.path_extern_supervisor, BACKUP_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_share(self):
|
def path_share(self) -> Path:
|
||||||
"""Return root share data folder."""
|
"""Return root share data folder."""
|
||||||
return Path(SUPERVISOR_DATA, SHARE_DATA)
|
return Path(SUPERVISOR_DATA, SHARE_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_apparmor(self):
|
def path_apparmor(self) -> Path:
|
||||||
"""Return root Apparmor profile folder."""
|
"""Return root Apparmor profile folder."""
|
||||||
return Path(SUPERVISOR_DATA, APPARMOR_DATA)
|
return Path(SUPERVISOR_DATA, APPARMOR_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_share(self):
|
def path_extern_share(self) -> PurePath:
|
||||||
"""Return root share data folder external for Docker."""
|
"""Return root share data folder external for Docker."""
|
||||||
return PurePath(self.path_extern_supervisor, SHARE_DATA)
|
return PurePath(self.path_extern_supervisor, SHARE_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_dns(self):
|
def path_extern_dns(self) -> str:
|
||||||
"""Return dns path external for Docker."""
|
"""Return dns path external for Docker."""
|
||||||
return str(PurePath(self.path_extern_supervisor, DNS_DATA))
|
return str(PurePath(self.path_extern_supervisor, DNS_DATA))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_dns(self):
|
def path_dns(self) -> Path:
|
||||||
"""Return dns path inside supervisor."""
|
"""Return dns path inside supervisor."""
|
||||||
return Path(SUPERVISOR_DATA, DNS_DATA)
|
return Path(SUPERVISOR_DATA, DNS_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addons_repositories(self):
|
def addons_repositories(self) -> List[str]:
|
||||||
"""Return list of custom Add-on repositories."""
|
"""Return list of custom Add-on repositories."""
|
||||||
return self._data[ATTR_ADDONS_CUSTOM_LIST]
|
return self._data[ATTR_ADDONS_CUSTOM_LIST]
|
||||||
|
|
||||||
def add_addon_repository(self, repo):
|
def add_addon_repository(self, repo: str) -> None:
|
||||||
"""Add a custom repository to list."""
|
"""Add a custom repository to list."""
|
||||||
if repo in self._data[ATTR_ADDONS_CUSTOM_LIST]:
|
if repo in self._data[ATTR_ADDONS_CUSTOM_LIST]:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._data[ATTR_ADDONS_CUSTOM_LIST].append(repo)
|
self._data[ATTR_ADDONS_CUSTOM_LIST].append(repo)
|
||||||
|
|
||||||
def drop_addon_repository(self, repo):
|
def drop_addon_repository(self, repo: str) -> None:
|
||||||
"""Remove a custom repository from list."""
|
"""Remove a custom repository from list."""
|
||||||
if repo not in self._data[ATTR_ADDONS_CUSTOM_LIST]:
|
if repo not in self._data[ATTR_ADDONS_CUSTOM_LIST]:
|
||||||
return
|
return
|
||||||
|
@ -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 = "224"
|
SUPERVISOR_VERSION = "225"
|
||||||
|
|
||||||
|
|
||||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||||
@ -362,6 +362,7 @@ class CoreStates(str, Enum):
|
|||||||
STARTUP = "startup"
|
STARTUP = "startup"
|
||||||
RUNNING = "running"
|
RUNNING = "running"
|
||||||
FREEZE = "freeze"
|
FREEZE = "freeze"
|
||||||
|
STOPPING = "stopping"
|
||||||
|
|
||||||
|
|
||||||
class LogLevel(str, Enum):
|
class LogLevel(str, Enum):
|
||||||
|
@ -48,6 +48,12 @@ class Core(CoreSysAttributes):
|
|||||||
"Docker version %s is not supported by Supervisor!",
|
"Docker version %s is not supported by Supervisor!",
|
||||||
self.sys_docker.info.version,
|
self.sys_docker.info.version,
|
||||||
)
|
)
|
||||||
|
elif self.sys_docker.info.inside_lxc:
|
||||||
|
self.healthy = False
|
||||||
|
_LOGGER.critical(
|
||||||
|
"Detected Docker running inside LXC. Running Home Assistant with the Supervisor on LXC is not supported!"
|
||||||
|
)
|
||||||
|
|
||||||
self.sys_docker.info.check_requirements()
|
self.sys_docker.info.check_requirements()
|
||||||
|
|
||||||
# Check if system is healthy
|
# Check if system is healthy
|
||||||
@ -181,7 +187,7 @@ class Core(CoreSysAttributes):
|
|||||||
async def stop(self):
|
async def stop(self):
|
||||||
"""Stop a running orchestration."""
|
"""Stop a running orchestration."""
|
||||||
# don't process scheduler anymore
|
# don't process scheduler anymore
|
||||||
self.sys_scheduler.suspend = True
|
self.state = CoreStates.STOPPING
|
||||||
|
|
||||||
# store new last boot / prevent time adjustments
|
# store new last boot / prevent time adjustments
|
||||||
if self.state == CoreStates.RUNNING:
|
if self.state == CoreStates.RUNNING:
|
||||||
@ -207,12 +213,17 @@ class Core(CoreSysAttributes):
|
|||||||
|
|
||||||
async def shutdown(self):
|
async def shutdown(self):
|
||||||
"""Shutdown all running containers in correct order."""
|
"""Shutdown all running containers in correct order."""
|
||||||
|
# don't process scheduler anymore
|
||||||
|
self.state = CoreStates.STOPPING
|
||||||
|
|
||||||
|
# Shutdown Application Add-ons, using Home Assistant API
|
||||||
await self.sys_addons.shutdown(STARTUP_APPLICATION)
|
await self.sys_addons.shutdown(STARTUP_APPLICATION)
|
||||||
|
|
||||||
# Close Home Assistant
|
# Close Home Assistant
|
||||||
with suppress(HassioError):
|
with suppress(HassioError):
|
||||||
await self.sys_homeassistant.stop()
|
await self.sys_homeassistant.stop()
|
||||||
|
|
||||||
|
# Shutdown System Add-ons
|
||||||
await self.sys_addons.shutdown(STARTUP_SERVICES)
|
await self.sys_addons.shutdown(STARTUP_SERVICES)
|
||||||
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)
|
||||||
|
@ -10,7 +10,6 @@ from .config import CoreConfig
|
|||||||
from .const import UpdateChannels
|
from .const import UpdateChannels
|
||||||
from .docker import DockerAPI
|
from .docker import DockerAPI
|
||||||
from .misc.hardware import Hardware
|
from .misc.hardware import Hardware
|
||||||
from .misc.scheduler import Scheduler
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .addons import AddonManager
|
from .addons import AddonManager
|
||||||
@ -21,16 +20,17 @@ if TYPE_CHECKING:
|
|||||||
from .dbus import DBusManager
|
from .dbus import DBusManager
|
||||||
from .discovery import Discovery
|
from .discovery import Discovery
|
||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
from .hwmon import HwMonitor
|
from .misc.scheduler import Scheduler
|
||||||
|
from .misc.hwmon import HwMonitor
|
||||||
|
from .misc.secrets import SecretsManager
|
||||||
|
from .misc.tasks import Tasks
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
from .secrets import SecretsManager
|
|
||||||
from .services import ServiceManager
|
from .services import ServiceManager
|
||||||
from .snapshots import SnapshotManager
|
from .snapshots import SnapshotManager
|
||||||
from .supervisor import Supervisor
|
from .supervisor import Supervisor
|
||||||
from .store import StoreManager
|
from .store import StoreManager
|
||||||
from .tasks import Tasks
|
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .plugins import PluginManager
|
from .plugins import PluginManager
|
||||||
|
|
||||||
@ -58,7 +58,6 @@ class CoreSys:
|
|||||||
self._config: CoreConfig = CoreConfig()
|
self._config: CoreConfig = CoreConfig()
|
||||||
self._hardware: Hardware = Hardware()
|
self._hardware: Hardware = Hardware()
|
||||||
self._docker: DockerAPI = DockerAPI()
|
self._docker: DockerAPI = DockerAPI()
|
||||||
self._scheduler: Scheduler = Scheduler()
|
|
||||||
|
|
||||||
# Internal objects pointers
|
# Internal objects pointers
|
||||||
self._core: Optional[Core] = None
|
self._core: Optional[Core] = None
|
||||||
@ -77,6 +76,7 @@ class CoreSys:
|
|||||||
self._hassos: Optional[HassOS] = None
|
self._hassos: Optional[HassOS] = None
|
||||||
self._services: Optional[ServiceManager] = None
|
self._services: Optional[ServiceManager] = None
|
||||||
self._secrets: Optional[SecretsManager] = None
|
self._secrets: Optional[SecretsManager] = None
|
||||||
|
self._scheduler: Optional[Scheduler] = None
|
||||||
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
|
||||||
@ -127,8 +127,17 @@ class CoreSys:
|
|||||||
@property
|
@property
|
||||||
def scheduler(self) -> Scheduler:
|
def scheduler(self) -> Scheduler:
|
||||||
"""Return Scheduler object."""
|
"""Return Scheduler object."""
|
||||||
|
if self._scheduler is None:
|
||||||
|
raise RuntimeError("Scheduler not set!")
|
||||||
return self._scheduler
|
return self._scheduler
|
||||||
|
|
||||||
|
@scheduler.setter
|
||||||
|
def scheduler(self, value: Scheduler) -> None:
|
||||||
|
"""Set a Scheduler object."""
|
||||||
|
if self._scheduler:
|
||||||
|
raise RuntimeError("Scheduler already set!")
|
||||||
|
self._scheduler = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def core(self) -> Core:
|
def core(self) -> Core:
|
||||||
"""Return core object."""
|
"""Return core object."""
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
@ -46,6 +47,11 @@ class DockerInfo:
|
|||||||
|
|
||||||
return version_local >= version_min
|
return version_local >= version_min
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inside_lxc(self) -> bool:
|
||||||
|
"""Return True if the docker run inside lxc."""
|
||||||
|
return Path("/dev/lxd/sock").exists()
|
||||||
|
|
||||||
def check_requirements(self) -> None:
|
def check_requirements(self) -> None:
|
||||||
"""Show wrong configurations."""
|
"""Show wrong configurations."""
|
||||||
if self.storage != "overlay2":
|
if self.storage != "overlay2":
|
||||||
@ -95,7 +101,7 @@ class DockerAPI:
|
|||||||
version: str = "latest",
|
version: str = "latest",
|
||||||
dns: bool = True,
|
dns: bool = True,
|
||||||
ipv4: Optional[IPv4Address] = None,
|
ipv4: Optional[IPv4Address] = None,
|
||||||
**kwargs: Dict[str, Any],
|
**kwargs: Any,
|
||||||
) -> docker.models.containers.Container:
|
) -> docker.models.containers.Container:
|
||||||
"""Create a Docker container and run it.
|
"""Create a Docker container and run it.
|
||||||
|
|
||||||
@ -153,7 +159,7 @@ class DockerAPI:
|
|||||||
image: str,
|
image: str,
|
||||||
version: str = "latest",
|
version: str = "latest",
|
||||||
command: Optional[str] = None,
|
command: Optional[str] = None,
|
||||||
**kwargs: Dict[str, Any],
|
**kwargs: Any,
|
||||||
) -> CommandReturn:
|
) -> CommandReturn:
|
||||||
"""Create a temporary container and run command.
|
"""Create a temporary container and run command.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
self.lock: asyncio.Lock = asyncio.Lock()
|
self.lock: asyncio.Lock = asyncio.Lock()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self) -> str:
|
def timeout(self) -> int:
|
||||||
"""Return timeout for Docker actions."""
|
"""Return timeout for Docker actions."""
|
||||||
return 30
|
return 30
|
||||||
|
|
||||||
|
@ -91,10 +91,10 @@ class DockerNetwork:
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
ipv4 = str(ipv4) if ipv4 else None
|
ipv4_address = str(ipv4) if ipv4 else None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.network.connect(container, aliases=alias, ipv4_address=ipv4)
|
self.network.connect(container, aliases=alias, ipv4_address=ipv4_address)
|
||||||
except docker.errors.APIError as err:
|
except docker.errors.APIError as err:
|
||||||
_LOGGER.error("Can't link container to hassio-net: %s", err)
|
_LOGGER.error("Can't link container to hassio-net: %s", err)
|
||||||
raise DockerAPIError() from None
|
raise DockerAPIError() from None
|
||||||
|
@ -56,7 +56,7 @@ class HassOS(CoreSysAttributes):
|
|||||||
_LOGGER.error("No HassOS available")
|
_LOGGER.error("No HassOS available")
|
||||||
raise HassOSNotSupportedError()
|
raise HassOSNotSupportedError()
|
||||||
|
|
||||||
async def _download_raucb(self, version: str) -> None:
|
async def _download_raucb(self, version: str) -> Path:
|
||||||
"""Download rauc bundle (OTA) from github."""
|
"""Download rauc bundle (OTA) from github."""
|
||||||
url = URL_HASSOS_OTA.format(version=version, board=self.board)
|
url = URL_HASSOS_OTA.format(version=version, board=self.board)
|
||||||
raucb = Path(self.sys_config.path_tmp, f"hassos-{version}.raucb")
|
raucb = Path(self.sys_config.path_tmp, f"hassos-{version}.raucb")
|
||||||
@ -158,7 +158,7 @@ class HassOS(CoreSysAttributes):
|
|||||||
_LOGGER.error("HassOS update fails with: %s", self.sys_dbus.rauc.last_error)
|
_LOGGER.error("HassOS update fails with: %s", self.sys_dbus.rauc.last_error)
|
||||||
raise HassOSUpdateError()
|
raise HassOSUpdateError()
|
||||||
|
|
||||||
async def mark_healthy(self):
|
async def mark_healthy(self) -> None:
|
||||||
"""Set booted partition as good for rauc."""
|
"""Set booted partition as good for rauc."""
|
||||||
try:
|
try:
|
||||||
response = await self.sys_dbus.rauc.mark(RaucState.GOOD, "booted")
|
response = await self.sys_dbus.rauc.mark(RaucState.GOOD, "booted")
|
||||||
|
@ -253,9 +253,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
while True:
|
while True:
|
||||||
if not self.sys_updater.image_homeassistant:
|
if not self.sys_updater.image_homeassistant:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Found no information about Home Assistant. Retry in 15sec"
|
"Found no information about Home Assistant. Retry in 30sec"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(15)
|
await asyncio.sleep(30)
|
||||||
await self.sys_updater.reload()
|
await self.sys_updater.reload()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -72,7 +72,13 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
sessions = {}
|
sessions = {}
|
||||||
for session, valid in self.sessions.items():
|
for session, valid in self.sessions.items():
|
||||||
valid_dt = utc_from_timestamp(valid)
|
# check if timestamp valid, to avoid crash on malformed timestamp
|
||||||
|
try:
|
||||||
|
valid_dt = utc_from_timestamp(valid)
|
||||||
|
except OverflowError:
|
||||||
|
_LOGGER.warning("Session timestamp %f is invalid!", valid_dt)
|
||||||
|
continue
|
||||||
|
|
||||||
if valid_dt < now:
|
if valid_dt < now:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -103,7 +109,13 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return True if session valid and make it longer valid."""
|
"""Return True if session valid and make it longer valid."""
|
||||||
if session not in self.sessions:
|
if session not in self.sessions:
|
||||||
return False
|
return False
|
||||||
valid_until = utc_from_timestamp(self.sessions[session])
|
|
||||||
|
# check if timestamp valid, to avoid crash on malformed timestamp
|
||||||
|
try:
|
||||||
|
valid_until = utc_from_timestamp(self.sessions[session])
|
||||||
|
except OverflowError:
|
||||||
|
_LOGGER.warning("Session timestamp %f is invalid!", valid_until)
|
||||||
|
return False
|
||||||
|
|
||||||
# Is still valid?
|
# Is still valid?
|
||||||
if valid_until < utcnow():
|
if valid_until < utcnow():
|
||||||
|
@ -6,8 +6,8 @@ from typing import Optional
|
|||||||
|
|
||||||
import pyudev
|
import pyudev
|
||||||
|
|
||||||
from .coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .utils import AsyncCallFilter
|
from ..utils import AsyncCallFilter
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
@ -1,8 +1,10 @@
|
|||||||
"""Schedule for Supervisor."""
|
"""Schedule for Supervisor."""
|
||||||
import asyncio
|
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from ..const import CoreStates
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
INTERVAL = "interval"
|
INTERVAL = "interval"
|
||||||
@ -11,14 +13,13 @@ CALL = "callback"
|
|||||||
TASK = "task"
|
TASK = "task"
|
||||||
|
|
||||||
|
|
||||||
class Scheduler:
|
class Scheduler(CoreSysAttributes):
|
||||||
"""Schedule task inside Supervisor."""
|
"""Schedule task inside Supervisor."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize task schedule."""
|
"""Initialize task schedule."""
|
||||||
self.loop = asyncio.get_running_loop()
|
self.coresys: CoreSys = coresys
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self.suspend = False
|
|
||||||
|
|
||||||
def register_task(self, coro_callback, interval, repeat=True):
|
def register_task(self, coro_callback, interval, repeat=True):
|
||||||
"""Schedule a coroutine.
|
"""Schedule a coroutine.
|
||||||
@ -40,8 +41,8 @@ class Scheduler:
|
|||||||
"""Run a scheduled task."""
|
"""Run a scheduled task."""
|
||||||
data = self._data[task_id]
|
data = self._data[task_id]
|
||||||
|
|
||||||
if not self.suspend:
|
if self.sys_core.state == CoreStates.RUNNING:
|
||||||
self.loop.create_task(data[CALL]())
|
self.sys_create_task(data[CALL]())
|
||||||
|
|
||||||
if data[REPEAT]:
|
if data[REPEAT]:
|
||||||
self._schedule_task(data[INTERVAL], task_id)
|
self._schedule_task(data[INTERVAL], task_id)
|
||||||
@ -51,7 +52,7 @@ class Scheduler:
|
|||||||
def _schedule_task(self, interval, task_id):
|
def _schedule_task(self, interval, task_id):
|
||||||
"""Schedule a task on loop."""
|
"""Schedule a task on loop."""
|
||||||
if isinstance(interval, (int, float)):
|
if isinstance(interval, (int, float)):
|
||||||
job = self.loop.call_later(interval, self._run_task, task_id)
|
job = self.sys_loop.call_later(interval, self._run_task, task_id)
|
||||||
elif isinstance(interval, time):
|
elif isinstance(interval, time):
|
||||||
today = datetime.combine(date.today(), interval)
|
today = datetime.combine(date.today(), interval)
|
||||||
tomorrow = datetime.combine(date.today() + timedelta(days=1), interval)
|
tomorrow = datetime.combine(date.today() + timedelta(days=1), interval)
|
||||||
@ -62,7 +63,7 @@ class Scheduler:
|
|||||||
else:
|
else:
|
||||||
calc = tomorrow
|
calc = tomorrow
|
||||||
|
|
||||||
job = self.loop.call_at(calc.timestamp(), self._run_task, task_id)
|
job = self.sys_loop.call_at(calc.timestamp(), self._run_task, task_id)
|
||||||
else:
|
else:
|
||||||
_LOGGER.critical(
|
_LOGGER.critical(
|
||||||
"Unknown interval %s (type: %s) for scheduler %s",
|
"Unknown interval %s (type: %s) for scheduler %s",
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Union
|
from typing import Dict, Optional, Union
|
||||||
|
|
||||||
from ruamel.yaml import YAML, YAMLError
|
from ruamel.yaml import YAML, YAMLError
|
||||||
|
|
||||||
from .coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .utils import AsyncThrottle
|
from ..utils import AsyncThrottle
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class SecretsManager(CoreSysAttributes):
|
|||||||
"""Return path to secret file."""
|
"""Return path to secret file."""
|
||||||
return Path(self.sys_config.path_homeassistant, "secrets.yaml")
|
return Path(self.sys_config.path_homeassistant, "secrets.yaml")
|
||||||
|
|
||||||
def get(self, secret: str) -> Union[bool, float, int, str]:
|
def get(self, secret: str) -> Optional[Union[bool, float, int, str]]:
|
||||||
"""Get secret from store."""
|
"""Get secret from store."""
|
||||||
_LOGGER.info("Request secret %s", secret)
|
_LOGGER.info("Request secret %s", secret)
|
||||||
return self.secrets.get(secret)
|
return self.secrets.get(secret)
|
@ -2,8 +2,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .exceptions import (
|
from ..exceptions import (
|
||||||
AudioError,
|
AudioError,
|
||||||
CliError,
|
CliError,
|
||||||
CoreDNSError,
|
CoreDNSError,
|
@ -5,7 +5,7 @@ Code: https://github.com/home-assistant/plugin-audio
|
|||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path, PurePath
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Awaitable, Optional
|
from typing import Awaitable, Optional
|
||||||
|
|
||||||
@ -36,12 +36,12 @@ class Audio(JsonConfig, CoreSysAttributes):
|
|||||||
self.client_template: Optional[jinja2.Template] = None
|
self.client_template: Optional[jinja2.Template] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_pulse(self) -> Path:
|
def path_extern_pulse(self) -> PurePath:
|
||||||
"""Return path of pulse socket file."""
|
"""Return path of pulse socket file."""
|
||||||
return self.sys_config.path_extern_audio.joinpath("external")
|
return self.sys_config.path_extern_audio.joinpath("external")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_asound(self) -> Path:
|
def path_extern_asound(self) -> PurePath:
|
||||||
"""Return path of default asound config file."""
|
"""Return path of default asound config file."""
|
||||||
return self.sys_config.path_extern_audio.joinpath("asound")
|
return self.sys_config.path_extern_audio.joinpath("asound")
|
||||||
|
|
||||||
|
@ -390,6 +390,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
if name not in entry.names:
|
if name not in entry.names:
|
||||||
continue
|
continue
|
||||||
return entry
|
return entry
|
||||||
|
return None
|
||||||
|
|
||||||
def logs(self) -> Awaitable[bytes]:
|
def logs(self) -> Awaitable[bytes]:
|
||||||
"""Get CoreDNS docker logs.
|
"""Get CoreDNS docker logs.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import ATTR_ACCESS_TOKEN, ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION
|
from ..const import ATTR_ACCESS_TOKEN, ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION
|
||||||
from ..validate import dns_server_list, docker_image, token, simple_version
|
from ..validate import dns_server_list, docker_image, simple_version, token
|
||||||
|
|
||||||
SCHEMA_DNS_CONFIG = vol.Schema(
|
SCHEMA_DNS_CONFIG = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL
|
from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CoreStates
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..utils.dt import utcnow
|
from ..utils.dt import utcnow
|
||||||
from .snapshot import Snapshot
|
from .snapshot import Snapshot
|
||||||
@ -125,7 +125,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
snapshot = self._create_snapshot(name, SNAPSHOT_FULL, password)
|
snapshot = self._create_snapshot(name, SNAPSHOT_FULL, password)
|
||||||
_LOGGER.info("Full-Snapshot %s start", snapshot.slug)
|
_LOGGER.info("Full-Snapshot %s start", snapshot.slug)
|
||||||
try:
|
try:
|
||||||
self.sys_scheduler.suspend = True
|
self.sys_core.state = CoreStates.FREEZE
|
||||||
await self.lock.acquire()
|
await self.lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
@ -147,7 +147,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.sys_scheduler.suspend = False
|
self.sys_core.state = CoreStates.RUNNING
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
async def do_snapshot_partial(
|
async def do_snapshot_partial(
|
||||||
@ -164,7 +164,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
|
|
||||||
_LOGGER.info("Partial-Snapshot %s start", snapshot.slug)
|
_LOGGER.info("Partial-Snapshot %s start", snapshot.slug)
|
||||||
try:
|
try:
|
||||||
self.sys_scheduler.suspend = True
|
self.sys_core.state = CoreStates.FREEZE
|
||||||
await self.lock.acquire()
|
await self.lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
@ -196,7 +196,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.sys_scheduler.suspend = False
|
self.sys_core.state = CoreStates.RUNNING
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
async def do_restore_full(self, snapshot, password=None):
|
async def do_restore_full(self, snapshot, password=None):
|
||||||
@ -215,7 +215,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
|
|
||||||
_LOGGER.info("Full-Restore %s start", snapshot.slug)
|
_LOGGER.info("Full-Restore %s start", snapshot.slug)
|
||||||
try:
|
try:
|
||||||
self.sys_scheduler.suspend = True
|
self.sys_core.state = CoreStates.FREEZE
|
||||||
await self.lock.acquire()
|
await self.lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
@ -267,7 +267,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.sys_scheduler.suspend = False
|
self.sys_core.state = CoreStates.RUNNING
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
async def do_restore_partial(
|
async def do_restore_partial(
|
||||||
@ -287,7 +287,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
|
|
||||||
_LOGGER.info("Partial-Restore %s start", snapshot.slug)
|
_LOGGER.info("Partial-Restore %s start", snapshot.slug)
|
||||||
try:
|
try:
|
||||||
self.sys_scheduler.suspend = True
|
self.sys_core.state = CoreStates.FREEZE
|
||||||
await self.lock.acquire()
|
await self.lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
@ -339,5 +339,5 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.sys_scheduler.suspend = False
|
self.sys_core.state = CoreStates.RUNNING
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
@ -31,7 +31,7 @@ from ..const import (
|
|||||||
SNAPSHOT_FULL,
|
SNAPSHOT_FULL,
|
||||||
SNAPSHOT_PARTIAL,
|
SNAPSHOT_PARTIAL,
|
||||||
)
|
)
|
||||||
from ..validate import docker_image, network_port, repositories, complex_version
|
from ..validate import complex_version, docker_image, network_port, repositories
|
||||||
|
|
||||||
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
||||||
|
|
||||||
|
@ -20,21 +20,23 @@ from ..exceptions import (
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Use to convert GVariant into json
|
# Use to convert GVariant into json
|
||||||
RE_GVARIANT_TYPE: re.Match = re.compile(
|
RE_GVARIANT_TYPE: re.Pattern[Any] = re.compile(
|
||||||
r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|"
|
r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|"
|
||||||
r"string|objectpath|signature|@[asviumodf\{\}]+) "
|
r"string|objectpath|signature|@[asviumodf\{\}]+) "
|
||||||
)
|
)
|
||||||
RE_GVARIANT_VARIANT: re.Match = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(<|>)")
|
RE_GVARIANT_VARIANT: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(<|>)")
|
||||||
RE_GVARIANT_STRING_ESC: re.Match = re.compile(
|
RE_GVARIANT_STRING_ESC: re.Pattern[Any] = re.compile(
|
||||||
r"(?<=(?: |{|\[|\(|<))'[^']*?\"[^']*?'(?=(?:|]|}|,|\)|>))"
|
r"(?<=(?: |{|\[|\(|<))'[^']*?\"[^']*?'(?=(?:|]|}|,|\)|>))"
|
||||||
)
|
)
|
||||||
RE_GVARIANT_STRING: re.Match = re.compile(
|
RE_GVARIANT_STRING: re.Pattern[Any] = re.compile(
|
||||||
r"(?<=(?: |{|\[|\(|<))'(.*?)'(?=(?:|]|}|,|\)|>))"
|
r"(?<=(?: |{|\[|\(|<))'(.*?)'(?=(?:|]|}|,|\)|>))"
|
||||||
)
|
)
|
||||||
RE_GVARIANT_TUPLE_O: re.Match = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(\()")
|
RE_GVARIANT_TUPLE_O: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(\()")
|
||||||
RE_GVARIANT_TUPLE_C: re.Match = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(,?\))")
|
RE_GVARIANT_TUPLE_C: re.Pattern[Any] = re.compile(
|
||||||
|
r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(,?\))"
|
||||||
|
)
|
||||||
|
|
||||||
RE_MONITOR_OUTPUT: re.Match = re.compile(r".+?: (?P<signal>[^ ].+) (?P<data>.*)")
|
RE_MONITOR_OUTPUT: re.Pattern[Any] = re.compile(r".+?: (?P<signal>[^ ].+) (?P<data>.*)")
|
||||||
|
|
||||||
# Map GDBus to errors
|
# Map GDBus to errors
|
||||||
MAP_GDBUS_ERROR: Dict[str, Any] = {
|
MAP_GDBUS_ERROR: Dict[str, Any] = {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""Validate functions."""
|
"""Validate functions."""
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import re
|
import re
|
||||||
import uuid
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
from packaging import version as pkg_version
|
from packaging import version as pkg_version
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ACCESS_TOKEN,
|
ATTR_ACCESS_TOKEN,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user