diff --git a/API.md b/API.md index bb38ffe0f..1951dc0f6 100644 --- a/API.md +++ b/API.md @@ -269,7 +269,9 @@ Optional: { "version": "INSTALL_VERSION", "last_version": "LAST_VERSION", - "devices": [] + "devices": [""], + "image": "str", + "custom": "bool -> if custom image" } ``` @@ -291,9 +293,13 @@ Output the raw docker log ```json { "devices": [], + "image": "Optional|null", + "last_version": "Optional for custom image|null" } ``` +Image with `null` and last_version with `null` reset this options. + ### REST API addons - POST `/addons/reload` diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index 6ba15511a..cc791b4ce 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -5,7 +5,8 @@ import logging import voluptuous as vol from .util import api_process, api_process_raw, api_validate -from ..const import ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES +from ..const import ( + ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES, ATTR_IMAGE, ATTR_CUSTOM) from ..validate import HASS_DEVICES _LOGGER = logging.getLogger(__name__) @@ -13,6 +14,9 @@ _LOGGER = logging.getLogger(__name__) SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_DEVICES): HASS_DEVICES, + vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Any(None, vol.Coerce(str)), + vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): + vol.Any(None, vol.Coerce(str)), }) SCHEMA_VERSION = vol.Schema({ @@ -34,8 +38,10 @@ class APIHomeAssistant(object): """Return host information.""" return { ATTR_VERSION: self.homeassistant.version, - ATTR_LAST_VERSION: self.config.last_homeassistant, - ATTR_DEVICES: self.config.homeassistant_devices, + ATTR_LAST_VERSION: self.homeassistant.last_version, + ATTR_IMAGE: self.homeassistant.image, + ATTR_DEVICES: self.homeassistant.devices, + ATTR_CUSTOM: self.homeassistant.is_custom_image, } @api_process @@ -44,7 +50,11 @@ class APIHomeAssistant(object): body = await api_validate(SCHEMA_OPTIONS, request) if ATTR_DEVICES in body: - self.config.homeassistant_devices = body[ATTR_DEVICES] + self.homeassistant.devices = body[ATTR_DEVICES] + + if ATTR_IMAGE in body: + self.homeassistant.set_custom( + body[ATTR_IMAGE], body[ATTR_LAST_VERSION]) return True diff --git a/hassio/const.py b/hassio/const.py index c8ee16ad9..88ca1be6b 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -95,6 +95,7 @@ ATTR_SIZE = 'size' ATTR_TYPE = 'type' ATTR_TIMEOUT = 'timeout' ATTR_AUTO_UPDATE = 'auto_update' +ATTR_CUSTOM = 'custom' STARTUP_INITIALIZE = 'initialize' STARTUP_BEFORE = 'before' diff --git a/hassio/core.py b/hassio/core.py index eaba036e9..6a74386cc 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -13,13 +13,12 @@ from .const import ( RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT, RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE, STARTUP_INITIALIZE, RUN_RELOAD_SNAPSHOTS_TASKS, RUN_UPDATE_ADDONS_TASKS) +from .homeassistant import HomeAssistant from .scheduler import Scheduler -from .dock.homeassistant import DockerHomeAssistant from .dock.supervisor import DockerSupervisor from .snapshots import SnapshotsManager from .tasks import ( - hassio_update, homeassistant_watchdog, homeassistant_setup, - api_sessions_cleanup, addons_update) + hassio_update, homeassistant_watchdog, api_sessions_cleanup, addons_update) from .tools import get_local_ip, fetch_timezone _LOGGER = logging.getLogger(__name__) @@ -41,7 +40,9 @@ class HassIO(object): # init basic docker container self.supervisor = DockerSupervisor(config, loop, self.dock, self.stop) - self.homeassistant = DockerHomeAssistant(config, loop, self.dock) + + # init homeassistant + self.homeassistant = HomeAssistant(config, loop, self.dock) # init HostControl self.host_control = HostControl(loop) @@ -94,13 +95,8 @@ class HassIO(object): api_sessions_cleanup(self.config), RUN_CLEANUP_API_SESSIONS, now=True) - # first start of supervisor? - if not await self.homeassistant.exists(): - _LOGGER.info("No HomeAssistant docker found.") - await homeassistant_setup( - self.config, self.loop, self.homeassistant, self.websession) - else: - await self.homeassistant.attach() + # Load homeassistant + await self.homeassistant.prepare(): # Load addons await self.addons.prepare() @@ -132,6 +128,10 @@ class HassIO(object): loop=self.loop ) + # If laningpage / run upgrade in background + if self.homeassistant.version == 'landingpage': + self.loop.create_task(self.homeassistant.install()) + # start api await self.api.start() _LOGGER.info("Start hassio api on %s", self.config.api_endpoint) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 82525e5f5..e7c6721e3 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -1,8 +1,10 @@ """HomeAssistant control object.""" +import asyncio import logging import os -from .const import FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE +from .const import ( + FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION) from .dock.homeassistant import DockerHomeAssistant from .tools import JsonConfig from .validate import SCHEMA_HASS_CONFIG @@ -11,14 +13,24 @@ from .validate import SCHEMA_HASS_CONFIG class HomeAssistant(JsonConfig): """Hass core object for handle it.""" - def __init__(self, config, loop, dock): + def __init__(self, config, loop, dock, websession): """Initialize hass object.""" super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) self.config = config self.loop = loop + self.websession = websession + self.docker = DockerHomeAssistant(config, loop, dock, self) async def prepare(self): """Prepare HomeAssistant object.""" + if not await self.docker.exists(): + _LOGGER.info("No HomeAssistant docker %s found.", self.image) + if self.is_custom_image: + await self.install() + else: + await self.install_landingpage() + else: + await self.docker.attach() @property def version(self): @@ -28,6 +40,8 @@ class HomeAssistant(JsonConfig): @property def last_version(self): """Return last available version of homeassistant.""" + if self.is_custom_image: + return self._data.get(ATTR_LAST_VERSION) return self.config.last_homeassistant @property @@ -37,14 +51,10 @@ class HomeAssistant(JsonConfig): return self._data[ATTR_IMAGE] return os.environ['HOMEASSISTANT_REPOSITORY'] - @image.setter - def image(self, value): - """Set image name of hass containter.""" - if value is None: - self._data.pop(ATTR_IMAGE, None) - else: - self._data[ATTR_IMAGE] = value - self.save() + @property + def is_custom_image(self): + """Return True if a custom image is used.""" + return ATTR_IMAGE in self._data @property def devices(self): @@ -56,3 +66,57 @@ class HomeAssistant(JsonConfig): """Set extend device mapping.""" self._data[ATTR_DEVICES] = value self.save() + + def set_custom(self, image, version): + """Set a custom image for homeassistant.""" + # reset + if image is None and version is None: + self._data.pop(ATTR_IMAGE, None) + self._data.pop(ATTR_VERSION, None) + else: + if image: + self._data[ATTR_IMAGE] = image + if version: + self._data[ATTR_VERSION] = version + self.save() + + async def install_landingpage(self): + """Install a landingpage.""" + _LOGGER.info("Setup HomeAssistant landingpage") + while True: + if self.docker.install('landingpage'): + break + _LOGGER.warning("Fails install landingpage, retry after 60sec") + await asyncio.sleep(60, loop=self.loop) + + async def install(self): + """Install a landingpage.""" + _LOGGER.info("Setup HomeAssistant") + while True: + # read homeassistant tag and install it + if not self.last_version: + await self.config.fetch_update_infos(websession) + + tag = self.last_version + if tag and await self.docker.install(tag): + break + _LOGGER.warning("Error on install HomeAssistant. Retry in 60sec") + await asyncio.sleep(60, loop=loop) + + # store version + _LOGGER.info("HomeAssistant docker now installed") + + async def update(self, version=None): + """Update HomeAssistant version.""" + version = version or self.last_version + if version == self.version: + return True + + return self.docker.update(version) + + def run(self): + """Run HomeAssistant docker. + + Return a coroutine. + """ + return self.docker.run() diff --git a/hassio/snapshots/__init__.py b/hassio/snapshots/__init__.py index 29f3b277a..8d2bd3f91 100644 --- a/hassio/snapshots/__init__.py +++ b/hassio/snapshots/__init__.py @@ -47,7 +47,7 @@ class SnapshotsManager(object): # set general data snapshot.homeassistant_version = self.homeassistant.version - snapshot.homeassistant_devices = self.config.homeassistant_devices + snapshot.homeassistant_devices = self.homeassistant.devices snapshot.repositories = self.config.addons_repositories return snapshot @@ -198,8 +198,7 @@ class SnapshotsManager(object): await snapshot.restore_folders() # start homeassistant restore - self.config.homeassistant_devices = \ - snapshot.homeassistant_devices + self.homeassistant.devices = snapshot.homeassistant_devices task_hass = self.loop.create_task( self.homeassistant.update(snapshot.homeassistant_version)) @@ -281,8 +280,7 @@ class SnapshotsManager(object): await snapshot.restore_folders(folders) if homeassistant: - self.config.homeassistant_devices = \ - snapshot.homeassistant_devices + self.homeassistant.devices = snapshot.homeassistant_devices tasks.append(self.homeassistant.update( snapshot.homeassistant_version)) diff --git a/hassio/tasks.py b/hassio/tasks.py index 25409e16e..a3421b560 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -66,20 +66,3 @@ def homeassistant_watchdog(loop, homeassistant): loop.create_task(homeassistant.run()) return _homeassistant_watchdog - - -async def homeassistant_setup(config, loop, homeassistant, websession): - """Install a homeassistant docker container.""" - while True: - # read homeassistant tag and install it - if not config.last_homeassistant: - await config.fetch_update_infos(websession) - - tag = config.last_homeassistant - if tag and await homeassistant.install(tag): - break - _LOGGER.warning("Error on setup HomeAssistant. Retry in 60.") - await asyncio.sleep(60, loop=loop) - - # store version - _LOGGER.info("HomeAssistant docker now installed.") diff --git a/hassio/validate.py b/hassio/validate.py index f339a9348..88c2cf2ca 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -1,7 +1,7 @@ """Validate functions.""" import voluptuous as vol -from .const import ATTR_DEVICES, ATTR_IMAGE +from .const import ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) @@ -37,5 +37,6 @@ DOCKER_PORTS = vol.Schema({ SCHEMA_HASS_CONFIG = vol.Schema({ vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES, - vol.Optional(ATTR_IMAGE): vol.Coerce(str) + vol.inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str), + vol.inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str), })