mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-08-20 06:29:21 +00:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
30243c39e6 | ||
![]() |
d285fd4ad4 | ||
![]() |
7a0b9cc1ac | ||
![]() |
cc63008a86 | ||
![]() |
f9c7371140 |
@@ -1,7 +1,6 @@
|
||||
"""Main file for HassIO."""
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import hassio.bootstrap as bootstrap
|
||||
@@ -25,12 +24,7 @@ if __name__ == "__main__":
|
||||
|
||||
_LOGGER.info("Start Hassio task")
|
||||
loop.call_soon_threadsafe(loop.create_task, hassio.start())
|
||||
|
||||
try:
|
||||
loop.add_signal_handler(
|
||||
signal.SIGTERM, lambda: loop.create_task(hassio.stop()))
|
||||
except ValueError:
|
||||
_LOGGER.warning("Could not bind to SIGTERM")
|
||||
loop.call_soon_threadsafe(bootstrap.reg_signal, loop, hassio)
|
||||
|
||||
loop.run_forever()
|
||||
loop.close()
|
||||
|
@@ -47,7 +47,7 @@ class AddonManager(AddonsData):
|
||||
tasks = []
|
||||
for addon in self.list_removed:
|
||||
_LOGGER.info("Old addon %s found")
|
||||
tasks.append(self.loop.create_task(self.dockers[addon].remove()))
|
||||
tasks.append(self.loop.create_task(self.uninstall(addon)))
|
||||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks, loop=self.loop)
|
||||
|
@@ -5,11 +5,12 @@ import glob
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from .validate import validate_options, SCHEMA_ADDON_CONFIG
|
||||
from ..const import (
|
||||
FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON,
|
||||
ATTR_STARTUP, ATTR_BOOT, ATTR_MAP_SSL, ATTR_MAP_CONFIG, ATTR_OPTIONS,
|
||||
ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER, STARTUP_BEFORE, BOOT_AUTO,
|
||||
BOOT_MANUAL, DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA, ATTR_IMAGE)
|
||||
ATTR_PORTS, BOOT_AUTO, DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA,
|
||||
ATTR_IMAGE, ATTR_MAP_HASSIO)
|
||||
from ..config import Config
|
||||
from ..tools import read_json_file, write_json_file
|
||||
|
||||
@@ -17,32 +18,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ADDONS_REPO_PATTERN = "{}/*/config.json"
|
||||
|
||||
V_STR = 'str'
|
||||
V_INT = 'int'
|
||||
V_FLOAT = 'float'
|
||||
V_BOOL = 'bool'
|
||||
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_ADDON_CONFIG = vol.Schema({
|
||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
||||
vol.Required(ATTR_STARTUP):
|
||||
vol.In([STARTUP_BEFORE, STARTUP_AFTER, STARTUP_ONCE]),
|
||||
vol.Required(ATTR_BOOT):
|
||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_PORTS): dict,
|
||||
vol.Required(ATTR_MAP_CONFIG): vol.Boolean(),
|
||||
vol.Required(ATTR_MAP_SSL): vol.Boolean(),
|
||||
vol.Required(ATTR_OPTIONS): dict,
|
||||
vol.Required(ATTR_SCHEMA): {
|
||||
vol.Coerce(str): vol.In([V_STR, V_INT, V_FLOAT, V_BOOL])
|
||||
},
|
||||
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"),
|
||||
})
|
||||
|
||||
|
||||
class AddonsData(Config):
|
||||
"""Hold data for addons inside HassIO."""
|
||||
@@ -56,6 +31,8 @@ class AddonsData(Config):
|
||||
|
||||
def read_addons_repo(self):
|
||||
"""Read data from addons repository."""
|
||||
self._addons_data = {}
|
||||
|
||||
self._read_addons_folder(self.config.path_addons_repo)
|
||||
self._read_addons_folder(self.config.path_addons_custom)
|
||||
|
||||
@@ -213,6 +190,10 @@ class AddonsData(Config):
|
||||
"""Return True if ssl map is needed."""
|
||||
return self._addons_data[addon][ATTR_MAP_SSL]
|
||||
|
||||
def need_hassio(self, addon):
|
||||
"""Return True if hassio map is needed."""
|
||||
return self._addons_data[addon][ATTR_MAP_HASSIO]
|
||||
|
||||
def path_data(self, addon):
|
||||
"""Return addon data path inside supervisor."""
|
||||
return "{}/{}".format(
|
||||
@@ -229,35 +210,21 @@ class AddonsData(Config):
|
||||
|
||||
def write_addon_options(self, addon):
|
||||
"""Return True if addon options is written to data."""
|
||||
return write_json_file(
|
||||
self.path_addon_options(addon), self.get_options(addon))
|
||||
schema = self.get_schema(addon)
|
||||
options = self.get_options(addon)
|
||||
|
||||
try:
|
||||
schema(options)
|
||||
return write_json_file(self.path_addon_options(addon), options)
|
||||
except vol.Invalid as ex:
|
||||
_LOGGER.error("Addon %s have wrong options -> %s", addon,
|
||||
humanize_error(options, ex))
|
||||
|
||||
return False
|
||||
|
||||
def get_schema(self, addon):
|
||||
"""Create a schema for addon options."""
|
||||
raw_schema = self._addons_data[addon][ATTR_SCHEMA]
|
||||
|
||||
def validate(struct):
|
||||
"""Validate schema."""
|
||||
options = {}
|
||||
for key, value in struct.items():
|
||||
if key not in raw_schema:
|
||||
raise vol.Invalid("Unknown options {}.".format(key))
|
||||
|
||||
typ = raw_schema[key]
|
||||
try:
|
||||
if typ == V_STR:
|
||||
options[key] = str(value)
|
||||
elif typ == V_INT:
|
||||
options[key] = int(value)
|
||||
elif typ == V_FLOAT:
|
||||
options[key] = float(value)
|
||||
elif typ == V_BOOL:
|
||||
options[key] = vol.Boolean()(value)
|
||||
except TypeError:
|
||||
raise vol.Invalid(
|
||||
"Type error for {}.".format(key)) from None
|
||||
|
||||
return options
|
||||
|
||||
schema = vol.Schema(vol.All(dict(), validate))
|
||||
schema = vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
||||
return schema
|
||||
|
113
hassio/addons/validate.py
Normal file
113
hassio/addons/validate.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Validate addons options schema."""
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP,
|
||||
ATTR_BOOT, ATTR_MAP_SSL, ATTR_MAP_CONFIG, ATTR_OPTIONS,
|
||||
ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER, STARTUP_BEFORE, BOOT_AUTO,
|
||||
BOOT_MANUAL, ATTR_SCHEMA, ATTR_IMAGE, ATTR_MAP_HASSIO)
|
||||
|
||||
V_STR = 'str'
|
||||
V_INT = 'int'
|
||||
V_FLOAT = 'float'
|
||||
V_BOOL = 'bool'
|
||||
V_EMAIL = 'email'
|
||||
V_URL = 'url'
|
||||
|
||||
ADDON_ELEMENT = vol.In([V_STR, V_INT, V_FLOAT, V_BOOL, V_EMAIL, V_URL])
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_ADDON_CONFIG = vol.Schema({
|
||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
||||
vol.Required(ATTR_STARTUP):
|
||||
vol.In([STARTUP_BEFORE, STARTUP_AFTER, STARTUP_ONCE]),
|
||||
vol.Required(ATTR_BOOT):
|
||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_PORTS): dict,
|
||||
vol.Optional(ATTR_MAP_CONFIG, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_MAP_SSL, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_MAP_HASSIO, default=False): vol.Boolean(),
|
||||
vol.Required(ATTR_OPTIONS): dict,
|
||||
vol.Required(ATTR_SCHEMA): {
|
||||
vol.Coerce(str): vol.Any(ADDON_ELEMENT, [
|
||||
vol.Any(ADDON_ELEMENT, {vol.Coerce(str): ADDON_ELEMENT})
|
||||
])
|
||||
},
|
||||
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"),
|
||||
})
|
||||
|
||||
|
||||
def validate_options(raw_schema):
|
||||
"""Validate schema."""
|
||||
def validate(struct):
|
||||
"""Create schema validator for addons options."""
|
||||
options = {}
|
||||
|
||||
# read options
|
||||
for key, value in struct.items():
|
||||
if key not in raw_schema:
|
||||
raise vol.Invalid("Unknown options {}.".format(key))
|
||||
|
||||
typ = raw_schema[key]
|
||||
try:
|
||||
if isinstance(typ, list):
|
||||
# nested value
|
||||
options[key] = _nested_validate(typ[0], value)
|
||||
else:
|
||||
# normal value
|
||||
options[key] = _single_validate(typ, value)
|
||||
except (IndexError, KeyError):
|
||||
raise vol.Invalid(
|
||||
"Type error for {}.".format(key)) from None
|
||||
|
||||
return options
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
def _single_validate(typ, value):
|
||||
"""Validate a single element."""
|
||||
try:
|
||||
if typ == V_STR:
|
||||
return str(value)
|
||||
elif typ == V_INT:
|
||||
return int(value)
|
||||
elif typ == V_FLOAT:
|
||||
return float(value)
|
||||
elif typ == V_BOOL:
|
||||
return vol.Boolean()(value)
|
||||
elif typ == V_EMAIL:
|
||||
return vol.Email()(value)
|
||||
elif typ == V_URL:
|
||||
return vol.Url()(value)
|
||||
|
||||
raise vol.Invalid("Fatal error for {}.".format(value))
|
||||
except TypeError:
|
||||
raise vol.Invalid(
|
||||
"Type {} error for {}.".format(typ, value)) from None
|
||||
|
||||
|
||||
def _nested_validate(typ, data_list):
|
||||
"""Validate nested items."""
|
||||
options = []
|
||||
|
||||
for element in data_list:
|
||||
# dict list
|
||||
if isinstance(typ, dict):
|
||||
c_options = {}
|
||||
for c_key, c_value in element.items():
|
||||
if c_key not in typ:
|
||||
raise vol.Invalid(
|
||||
"Unknown nested options {}.".format(c_key))
|
||||
|
||||
c_options[c_key] = _single_validate(typ[c_key], c_value)
|
||||
options.append(c_options)
|
||||
# normal list
|
||||
else:
|
||||
options.append(_single_validate(typ, element))
|
||||
|
||||
return options
|
@@ -3,6 +3,7 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from .util import api_process, api_validate
|
||||
from ..const import (
|
||||
@@ -88,6 +89,14 @@ class APIAddons(object):
|
||||
if await self.addons.state(addon) == STATE_STARTED:
|
||||
raise RuntimeError("Addon is already running")
|
||||
|
||||
# validate options
|
||||
try:
|
||||
schema = self.addons.get_schema(addon)
|
||||
options = self.addons.get_options(addon)
|
||||
schema(options)
|
||||
except vol.Invalid as ex:
|
||||
raise RuntimeError(humanize_error(options, ex)) from None
|
||||
|
||||
return await asyncio.shield(
|
||||
self.addons.start(addon), loop=self.loop)
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
import signal
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
|
||||
@@ -81,3 +82,24 @@ def check_environment():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def reg_signal(loop, hassio):
|
||||
"""Register SIGTERM, SIGKILL to stop system."""
|
||||
try:
|
||||
loop.add_signal_handler(
|
||||
signal.SIGTERM, lambda: loop.create_task(hassio.stop()))
|
||||
except (ValueError, RuntimeError):
|
||||
_LOGGER.warning("Could not bind to SIGTERM")
|
||||
|
||||
try:
|
||||
loop.add_signal_handler(
|
||||
signal.SIGHUP, lambda: loop.create_task(hassio.stop()))
|
||||
except (ValueError, RuntimeError):
|
||||
_LOGGER.warning("Could not bind to SIGHUP")
|
||||
|
||||
try:
|
||||
loop.add_signal_handler(
|
||||
signal.SIGINT, lambda: loop.create_task(hassio.stop()))
|
||||
except (ValueError, RuntimeError):
|
||||
_LOGGER.warning("Could not bind to SIGINT")
|
||||
|
@@ -22,6 +22,8 @@ ADDONS_CUSTOM = "{}/addons_custom"
|
||||
|
||||
UPSTREAM_BETA = 'upstream_beta'
|
||||
|
||||
API_ENDPOINT = 'api_endpoint'
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""Hold all config data."""
|
||||
@@ -78,6 +80,16 @@ class CoreConfig(Config):
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def api_endpoint(self):
|
||||
"""Return IP address of api endpoint."""
|
||||
return self._data[API_ENDPOINT]
|
||||
|
||||
@api_endpoint.setter
|
||||
def api_endpoint(self, value):
|
||||
"""Store IP address of api endpoint."""
|
||||
self._data[API_ENDPOINT] = value
|
||||
|
||||
@property
|
||||
def upstream_beta(self):
|
||||
"""Return True if we run in beta upstream."""
|
||||
@@ -117,10 +129,15 @@ class CoreConfig(Config):
|
||||
"""Actual version of hassio."""
|
||||
return self._data.get(HASSIO_CURRENT)
|
||||
|
||||
@property
|
||||
def path_hassio_docker(self):
|
||||
"""Return hassio data path extern for docker."""
|
||||
return os.environ['SUPERVISOR_SHARE']
|
||||
|
||||
@property
|
||||
def path_config_docker(self):
|
||||
"""Return config path extern for docker."""
|
||||
return HOMEASSISTANT_CONFIG.format(os.environ['SUPERVISOR_SHARE'])
|
||||
return HOMEASSISTANT_CONFIG.format(self.path_hassio_docker)
|
||||
|
||||
@property
|
||||
def path_config(self):
|
||||
@@ -130,7 +147,7 @@ class CoreConfig(Config):
|
||||
@property
|
||||
def path_ssl_docker(self):
|
||||
"""Return SSL path extern for docker."""
|
||||
return HASSIO_SSL.format(os.environ['SUPERVISOR_SHARE'])
|
||||
return HASSIO_SSL.format(self.path_hassio_docker)
|
||||
|
||||
@property
|
||||
def path_ssl(self):
|
||||
@@ -155,4 +172,4 @@ class CoreConfig(Config):
|
||||
@property
|
||||
def path_addons_data_docker(self):
|
||||
"""Return root addon data folder extern for docker."""
|
||||
return ADDONS_DATA.format(os.environ['SUPERVISOR_SHARE'])
|
||||
return ADDONS_DATA.format(self.path_hassio_docker)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
"""Const file for HassIO."""
|
||||
HASSIO_VERSION = '0.7'
|
||||
HASSIO_VERSION = '0.9'
|
||||
|
||||
URL_HASSIO_VERSION = \
|
||||
'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json'
|
||||
@@ -13,6 +13,7 @@ DOCKER_REPO = "pvizeli"
|
||||
HASSIO_SHARE = "/data"
|
||||
|
||||
RUN_UPDATE_INFO_TASKS = 28800
|
||||
RUN_UPDATE_SUPERVISOR_TASKS = 29100
|
||||
RUN_RELOAD_ADDONS_TASKS = 28800
|
||||
|
||||
RESTART_EXIT_CODE = 100
|
||||
@@ -42,6 +43,7 @@ ATTR_BOOT = 'boot'
|
||||
ATTR_PORTS = 'ports'
|
||||
ATTR_MAP_CONFIG = 'map_config'
|
||||
ATTR_MAP_SSL = 'map_ssl'
|
||||
ATTR_MAP_HASSIO = 'map_hassio'
|
||||
ATTR_OPTIONS = 'options'
|
||||
ATTR_INSTALLED = 'installed'
|
||||
ATTR_STATE = 'state'
|
||||
|
@@ -11,11 +11,11 @@ from .api import RestAPI
|
||||
from .host_controll import HostControll
|
||||
from .const import (
|
||||
SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS,
|
||||
STARTUP_AFTER, STARTUP_BEFORE)
|
||||
RUN_UPDATE_SUPERVISOR_TASKS, STARTUP_AFTER, STARTUP_BEFORE)
|
||||
from .scheduler import Scheduler
|
||||
from .dock.homeassistant import DockerHomeAssistant
|
||||
from .dock.supervisor import DockerSupervisor
|
||||
from .tools import get_arch_from_image
|
||||
from .tools import get_arch_from_image, get_local_ip
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,6 +52,9 @@ class HassIO(object):
|
||||
await self.supervisor.attach()
|
||||
await self.supervisor.cleanup()
|
||||
|
||||
# set api endpoint
|
||||
self.config.api_endpoint = await get_local_ip(self.loop)
|
||||
|
||||
# hostcontroll
|
||||
host_info = await self.host_controll.info()
|
||||
if host_info:
|
||||
@@ -72,7 +75,7 @@ class HassIO(object):
|
||||
# schedule update info tasks
|
||||
self.scheduler.register_task(
|
||||
self.config.fetch_update_infos, RUN_UPDATE_INFO_TASKS,
|
||||
first_run=True)
|
||||
now=True)
|
||||
|
||||
# first start of supervisor?
|
||||
if not await self.homeassistant.exists():
|
||||
@@ -85,12 +88,17 @@ class HassIO(object):
|
||||
|
||||
# schedule addon update task
|
||||
self.scheduler.register_task(
|
||||
self.addons.relaod, RUN_RELOAD_ADDONS_TASKS, first_run=True)
|
||||
self.addons.relaod, RUN_RELOAD_ADDONS_TASKS, now=True)
|
||||
|
||||
# schedule self update task
|
||||
self.scheduler.register_task(
|
||||
self._hassio_update, RUN_UPDATE_SUPERVISOR_TASKS)
|
||||
|
||||
async def start(self):
|
||||
"""Start HassIO orchestration."""
|
||||
# start api
|
||||
await self.api.start()
|
||||
_LOGGER.info("Start hassio api on %s", self.config.api_endpoint)
|
||||
|
||||
# HomeAssistant is already running / supervisor have only reboot
|
||||
if await self.homeassistant.is_running():
|
||||
@@ -108,6 +116,10 @@ class HassIO(object):
|
||||
|
||||
async def stop(self, exit_code=0):
|
||||
"""Stop a running orchestration."""
|
||||
# don't process scheduler anymore
|
||||
self.scheduler.stop()
|
||||
|
||||
# process stop task pararell
|
||||
tasks = [self.websession.close(), self.api.stop()]
|
||||
await asyncio.wait(tasks, loop=self.loop)
|
||||
|
||||
@@ -129,3 +141,12 @@ class HassIO(object):
|
||||
|
||||
# store version
|
||||
_LOGGER.info("HomeAssistant docker now installed.")
|
||||
|
||||
async def _hassio_update(self):
|
||||
"""Check and run update of supervisor hassio."""
|
||||
if self.config.current_hassio == self.supervisor.version:
|
||||
return
|
||||
|
||||
_LOGGER.info(
|
||||
"Found new HassIO version %s.", self.config.current_hassio)
|
||||
await self.supervisor.update(self.config.current_hassio)
|
||||
|
@@ -138,8 +138,6 @@ class DockerBase(object):
|
||||
return False
|
||||
|
||||
async with self._lock:
|
||||
_LOGGER.info("Run docker image %s with version %s",
|
||||
self.image, self.version)
|
||||
return await self.loop.run_in_executor(None, self._run)
|
||||
|
||||
def _run(self):
|
||||
@@ -167,6 +165,8 @@ class DockerBase(object):
|
||||
if not self.container:
|
||||
return
|
||||
|
||||
_LOGGER.info("Stop %s docker application.", self.image)
|
||||
|
||||
self.container.reload()
|
||||
if self.container.status == 'running':
|
||||
with suppress(docker.errors.DockerException):
|
||||
|
@@ -52,6 +52,11 @@ class DockerAddon(DockerBase):
|
||||
self.config.path_ssl_docker: {
|
||||
'bind': '/ssl', 'mode': 'rw'
|
||||
}})
|
||||
if self.addons_data.need_hassio(self.addon):
|
||||
volumes.update({
|
||||
self.config.path_hassio_docker: {
|
||||
'bind': '/hassio', 'mode': 'rw'
|
||||
}})
|
||||
|
||||
try:
|
||||
self.container = self.dock.containers.run(
|
||||
@@ -60,15 +65,15 @@ class DockerAddon(DockerBase):
|
||||
detach=True,
|
||||
network_mode='bridge',
|
||||
ports=self.addons_data.get_ports(self.addon),
|
||||
restart_policy={
|
||||
"Name": "on-failure",
|
||||
"MaximumRetryCount": 10,
|
||||
},
|
||||
volumes=volumes,
|
||||
)
|
||||
|
||||
self.version = get_version_from_env(
|
||||
self.container.attrs['Config']['Env'])
|
||||
|
||||
_LOGGER.info("Start docker addon %s with version %s",
|
||||
self.image, self.version)
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
||||
return False
|
||||
|
@@ -4,7 +4,7 @@ import logging
|
||||
import docker
|
||||
|
||||
from . import DockerBase
|
||||
from ..tools import get_version_from_env, get_local_ip
|
||||
from ..tools import get_version_from_env
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,8 +31,6 @@ class DockerHomeAssistant(DockerBase):
|
||||
if self._is_running():
|
||||
return
|
||||
|
||||
api_endpoint = get_local_ip(self.loop)
|
||||
|
||||
# cleanup old container
|
||||
self._stop()
|
||||
|
||||
@@ -43,12 +41,8 @@ class DockerHomeAssistant(DockerBase):
|
||||
detach=True,
|
||||
privileged=True,
|
||||
network_mode='host',
|
||||
restart_policy={
|
||||
"Name": "always",
|
||||
"MaximumRetryCount": 10,
|
||||
},
|
||||
environment={
|
||||
'HASSIO': api_endpoint,
|
||||
'HASSIO': self.config.api_endpoint,
|
||||
},
|
||||
volumes={
|
||||
self.config.path_config_docker:
|
||||
@@ -59,6 +53,10 @@ class DockerHomeAssistant(DockerBase):
|
||||
|
||||
self.version = get_version_from_env(
|
||||
self.container.attrs['Config']['Env'])
|
||||
|
||||
_LOGGER.info("Start docker addon %s with version %s",
|
||||
self.image, self.version)
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
||||
return False
|
||||
|
@@ -37,6 +37,9 @@ class DockerSupervisor(DockerBase):
|
||||
if await self.loop.run_in_executor(None, self._install, tag):
|
||||
self.config.hassio_cleanup = old_version
|
||||
self.loop.create_task(self.hassio.stop(RESTART_EXIT_CODE))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def cleanup(self):
|
||||
"""Check if old supervisor version exists and cleanup."""
|
||||
|
@@ -16,9 +16,14 @@ class Scheduler(object):
|
||||
"""Initialize task schedule."""
|
||||
self.loop = loop
|
||||
self._data = {}
|
||||
self._stop = False
|
||||
|
||||
def stop(self):
|
||||
"""Stop to execute tasks in scheduler."""
|
||||
self._stop = True
|
||||
|
||||
def register_task(self, coro_callback, seconds, repeat=True,
|
||||
first_run=False):
|
||||
now=False):
|
||||
"""Schedule a coroutine.
|
||||
|
||||
The coroutien need to be a callback without arguments.
|
||||
@@ -34,7 +39,7 @@ class Scheduler(object):
|
||||
self._data[idx] = opts
|
||||
|
||||
# schedule task
|
||||
if first_run:
|
||||
if now:
|
||||
self._run_task(idx)
|
||||
else:
|
||||
task = self.loop.call_later(seconds, self._run_task, idx)
|
||||
@@ -46,6 +51,10 @@ class Scheduler(object):
|
||||
"""Run a scheduled task."""
|
||||
data = self._data.pop(idx)
|
||||
|
||||
# stop execute tasks
|
||||
if self._stop:
|
||||
return
|
||||
|
||||
self.loop.create_task(data[CALL]())
|
||||
|
||||
if data[REPEAT]:
|
||||
|
@@ -55,19 +55,23 @@ def get_version_from_env(env_list):
|
||||
def get_local_ip(loop):
|
||||
"""Retrieve local IP address.
|
||||
|
||||
Need run inside executor.
|
||||
Return a future.
|
||||
"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
def local_ip():
|
||||
"""Return local ip."""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
# Use Google Public DNS server to determine own IP
|
||||
sock.connect(('8.8.8.8', 80))
|
||||
# Use Google Public DNS server to determine own IP
|
||||
sock.connect(('8.8.8.8', 80))
|
||||
|
||||
return sock.getsockname()[0]
|
||||
except socket.error:
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
finally:
|
||||
sock.close()
|
||||
return sock.getsockname()[0]
|
||||
except socket.error:
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
return loop.run_in_executor(None, local_ip)
|
||||
|
||||
|
||||
def write_json_file(jsonfile, data):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"hassio_tag": "0.7",
|
||||
"hassio_tag": "0.9",
|
||||
"homeassistant_tag": "0.42.3",
|
||||
"resinos_version": "0.3",
|
||||
"resinhup_version": "0.1"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"hassio_tag": "0.7",
|
||||
"hassio_tag": "0.9",
|
||||
"homeassistant_tag": "0.42.3",
|
||||
"resinos_version": "0.3",
|
||||
"resinhup_version": "0.1"
|
||||
|
Reference in New Issue
Block a user