mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-17 06:06:29 +00:00
Merge pull request #4 from pvizeli/selfupdate
Allow supervisor to update itself.
This commit is contained in:
commit
71590f90ae
6
API.md
6
API.md
@ -11,7 +11,6 @@ Communicate over unix socket with a host daemon.
|
|||||||
# reboot
|
# reboot
|
||||||
# shutdown
|
# shutdown
|
||||||
# host-update [v]
|
# host-update [v]
|
||||||
# supervisor-update [v]
|
|
||||||
|
|
||||||
# network info
|
# network info
|
||||||
# network hostname xy
|
# network hostname xy
|
||||||
@ -24,9 +23,8 @@ Communicate over unix socket with a host daemon.
|
|||||||
|
|
||||||
level:
|
level:
|
||||||
- 1: power functions
|
- 1: power functions
|
||||||
- 2: supervisor update
|
- 2: host update
|
||||||
- 4: host update
|
- 4: network functions
|
||||||
- 8: network functions
|
|
||||||
|
|
||||||
Answer:
|
Answer:
|
||||||
```
|
```
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
import hassio.bootstrap as bootstrap
|
import hassio.bootstrap as bootstrap
|
||||||
import hassio.core as core
|
import hassio.core as core
|
||||||
@ -33,4 +34,6 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
_LOGGER.info("Close Hassio")
|
_LOGGER.info("Close Hassio")
|
||||||
|
sys.exit(hassio.exit_code)
|
||||||
|
@ -41,10 +41,10 @@ class RestAPI(object):
|
|||||||
self.webapp.router.add_get('/network/info', api_net.info)
|
self.webapp.router.add_get('/network/info', api_net.info)
|
||||||
self.webapp.router.add_get('/network/options', api_net.options)
|
self.webapp.router.add_get('/network/options', api_net.options)
|
||||||
|
|
||||||
def register_supervisor(self, host_controll, addons):
|
def register_supervisor(self, supervisor, addons):
|
||||||
"""Register supervisor function."""
|
"""Register supervisor function."""
|
||||||
api_supervisor = APISupervisor(
|
api_supervisor = APISupervisor(
|
||||||
self.config, self.loop, host_controll, addons)
|
self.config, self.loop, supervisor, addons)
|
||||||
|
|
||||||
self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping)
|
self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping)
|
||||||
self.webapp.router.add_get('/supervisor/info', api_supervisor.info)
|
self.webapp.router.add_get('/supervisor/info', api_supervisor.info)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
"""Init file for HassIO supervisor rest api."""
|
"""Init file for HassIO supervisor rest api."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .util import api_process, api_process_hostcontroll, api_validate
|
from .util import api_process, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION)
|
ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION)
|
||||||
|
|
||||||
@ -22,11 +23,11 @@ SCHEMA_VERSION = vol.Schema({
|
|||||||
class APISupervisor(object):
|
class APISupervisor(object):
|
||||||
"""Handle rest api for supervisor functions."""
|
"""Handle rest api for supervisor functions."""
|
||||||
|
|
||||||
def __init__(self, config, loop, host_controll, addons):
|
def __init__(self, config, loop, supervisor, addons):
|
||||||
"""Initialize supervisor rest api part."""
|
"""Initialize supervisor rest api part."""
|
||||||
self.config = config
|
self.config = config
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.host_controll = host_controll
|
self.supervisor = supervisor
|
||||||
self.addons = addons
|
self.addons = addons
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@ -55,13 +56,13 @@ class APISupervisor(object):
|
|||||||
|
|
||||||
return self.config.save()
|
return self.config.save()
|
||||||
|
|
||||||
@api_process_hostcontroll
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request):
|
||||||
"""Update host OS."""
|
"""Update supervisor OS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.config.current_hassio)
|
version = body.get(ATTR_VERSION, self.config.current_hassio)
|
||||||
|
|
||||||
if version == HASSIO_VERSION:
|
if version == self.supervisor.version:
|
||||||
raise RuntimeError("Version is already in use")
|
raise RuntimeError("Version is already in use")
|
||||||
|
|
||||||
return await self.host_controll.supervisor_update(version=version)
|
return await asyncio.shield(self.supervisor.update(version))
|
||||||
|
@ -14,6 +14,7 @@ HOMEASSISTANT_CURRENT = 'homeassistant_current'
|
|||||||
|
|
||||||
HASSIO_SSL = "{}/ssl"
|
HASSIO_SSL = "{}/ssl"
|
||||||
HASSIO_CURRENT = 'hassio_current'
|
HASSIO_CURRENT = 'hassio_current'
|
||||||
|
HASSIO_CLEANUP = 'hassio_cleanup'
|
||||||
|
|
||||||
ADDONS_REPO = "{}/addons"
|
ADDONS_REPO = "{}/addons"
|
||||||
ADDONS_DATA = "{}/addons_data"
|
ADDONS_DATA = "{}/addons_data"
|
||||||
@ -87,6 +88,20 @@ class CoreConfig(Config):
|
|||||||
"""Set beta upstream mode."""
|
"""Set beta upstream mode."""
|
||||||
self._data[UPSTREAM_BETA] = bool(value)
|
self._data[UPSTREAM_BETA] = bool(value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hassio_cleanup(self):
|
||||||
|
"""Return Version they need to cleanup."""
|
||||||
|
return self._data.get(HASSIO_CLEANUP)
|
||||||
|
|
||||||
|
@hassio_cleanup.setter
|
||||||
|
def hassio_cleanup(self, version):
|
||||||
|
"""Set or remove cleanup flag."""
|
||||||
|
if version is None:
|
||||||
|
self._data.pop(HASSIO_CLEANUP, None)
|
||||||
|
else:
|
||||||
|
self._data[HASSIO_CLEANUP] = version
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant_image(self):
|
def homeassistant_image(self):
|
||||||
"""Return docker homeassistant repository."""
|
"""Return docker homeassistant repository."""
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Const file for HassIO."""
|
"""Const file for HassIO."""
|
||||||
HASSIO_VERSION = '0.6'
|
HASSIO_VERSION = '0.7'
|
||||||
|
|
||||||
URL_HASSIO_VERSION = \
|
URL_HASSIO_VERSION = \
|
||||||
'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json'
|
'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json'
|
||||||
@ -15,6 +15,8 @@ HASSIO_SHARE = "/data"
|
|||||||
RUN_UPDATE_INFO_TASKS = 28800
|
RUN_UPDATE_INFO_TASKS = 28800
|
||||||
RUN_RELOAD_ADDONS_TASKS = 28800
|
RUN_RELOAD_ADDONS_TASKS = 28800
|
||||||
|
|
||||||
|
RESTART_EXIT_CODE = 100
|
||||||
|
|
||||||
FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE)
|
FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE)
|
||||||
FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE)
|
FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE)
|
||||||
|
|
||||||
@ -49,7 +51,9 @@ ATTR_IMAGE = 'image'
|
|||||||
STARTUP_BEFORE = 'before'
|
STARTUP_BEFORE = 'before'
|
||||||
STARTUP_AFTER = 'after'
|
STARTUP_AFTER = 'after'
|
||||||
STARTUP_ONCE = 'once'
|
STARTUP_ONCE = 'once'
|
||||||
|
|
||||||
BOOT_AUTO = 'auto'
|
BOOT_AUTO = 'auto'
|
||||||
BOOT_MANUAL = 'manual'
|
BOOT_MANUAL = 'manual'
|
||||||
|
|
||||||
STATE_STARTED = 'started'
|
STATE_STARTED = 'started'
|
||||||
STATE_STOPPED = 'stopped'
|
STATE_STOPPED = 'stopped'
|
||||||
|
@ -25,6 +25,7 @@ class HassIO(object):
|
|||||||
|
|
||||||
def __init__(self, loop):
|
def __init__(self, loop):
|
||||||
"""Initialize hassio object."""
|
"""Initialize hassio object."""
|
||||||
|
self.exit_code = 0
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.websession = aiohttp.ClientSession(loop=self.loop)
|
self.websession = aiohttp.ClientSession(loop=self.loop)
|
||||||
self.config = bootstrap.initialize_system_data(self.websession)
|
self.config = bootstrap.initialize_system_data(self.websession)
|
||||||
@ -35,7 +36,7 @@ class HassIO(object):
|
|||||||
|
|
||||||
# init basic docker container
|
# init basic docker container
|
||||||
self.supervisor = DockerSupervisor(
|
self.supervisor = DockerSupervisor(
|
||||||
self.config, self.loop, self.dock)
|
self.config, self.loop, self.dock, self)
|
||||||
self.homeassistant = DockerHomeAssistant(
|
self.homeassistant = DockerHomeAssistant(
|
||||||
self.config, self.loop, self.dock)
|
self.config, self.loop, self.dock)
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ class HassIO(object):
|
|||||||
"""Setup HassIO orchestration."""
|
"""Setup HassIO orchestration."""
|
||||||
# supervisor
|
# supervisor
|
||||||
await self.supervisor.attach()
|
await self.supervisor.attach()
|
||||||
|
await self.supervisor.cleanup()
|
||||||
|
|
||||||
# hostcontroll
|
# hostcontroll
|
||||||
host_info = await self.host_controll.info()
|
host_info = await self.host_controll.info()
|
||||||
@ -63,7 +65,7 @@ class HassIO(object):
|
|||||||
# rest api views
|
# rest api views
|
||||||
self.api.register_host(self.host_controll)
|
self.api.register_host(self.host_controll)
|
||||||
self.api.register_network(self.host_controll)
|
self.api.register_network(self.host_controll)
|
||||||
self.api.register_supervisor(self.host_controll, self.addons)
|
self.api.register_supervisor(self.supervisor, self.addons)
|
||||||
self.api.register_homeassistant(self.homeassistant)
|
self.api.register_homeassistant(self.homeassistant)
|
||||||
self.api.register_addons(self.addons)
|
self.api.register_addons(self.addons)
|
||||||
|
|
||||||
@ -104,11 +106,12 @@ class HassIO(object):
|
|||||||
# start addon mark as after
|
# start addon mark as after
|
||||||
await self.addons.auto_boot(STARTUP_AFTER)
|
await self.addons.auto_boot(STARTUP_AFTER)
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self, exit_code=0):
|
||||||
"""Stop a running orchestration."""
|
"""Stop a running orchestration."""
|
||||||
tasks = [self.websession.close(), self.api.stop()]
|
tasks = [self.websession.close(), self.api.stop()]
|
||||||
await asyncio.wait(tasks, loop=self.loop)
|
await asyncio.wait(tasks, loop=self.loop)
|
||||||
|
|
||||||
|
self.exit_code = exit_code
|
||||||
self.loop.stop()
|
self.loop.stop()
|
||||||
|
|
||||||
async def _setup_homeassistant(self):
|
async def _setup_homeassistant(self):
|
||||||
|
@ -209,10 +209,7 @@ class DockerBase(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def update(self, tag):
|
async def update(self, tag):
|
||||||
"""Update a docker image.
|
"""Update a docker image."""
|
||||||
|
|
||||||
Return a Future.
|
|
||||||
"""
|
|
||||||
if self._lock.locked():
|
if self._lock.locked():
|
||||||
_LOGGER.error("Can't excute update while a task is in progress")
|
_LOGGER.error("Can't excute update while a task is in progress")
|
||||||
return False
|
return False
|
||||||
|
@ -1,17 +1,68 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for HassIO docker object."""
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import docker
|
||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
|
from ..const import RESTART_EXIT_CODE
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DockerSupervisor(DockerBase):
|
class DockerSupervisor(DockerBase):
|
||||||
"""Docker hassio wrapper for HomeAssistant."""
|
"""Docker hassio wrapper for HomeAssistant."""
|
||||||
|
|
||||||
|
def __init__(self, config, loop, dock, hassio, image=None):
|
||||||
|
"""Initialize docker base wrapper."""
|
||||||
|
super().__init__(config, loop, dock, image=image)
|
||||||
|
|
||||||
|
self.hassio = hassio
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker_name(self):
|
def docker_name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return os.environ['SUPERVISOR_NAME']
|
return os.environ['SUPERVISOR_NAME']
|
||||||
|
|
||||||
|
async def update(self, tag):
|
||||||
|
"""Update a supervisor docker image."""
|
||||||
|
if self._lock.locked():
|
||||||
|
_LOGGER.error("Can't excute update while a task is in progress")
|
||||||
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info("Update supervisor docker to %s:%s", self.image, tag)
|
||||||
|
old_version = self.version
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
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))
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Check if old supervisor version exists and cleanup."""
|
||||||
|
if not self.config.hassio_cleanup:
|
||||||
|
return
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
if await self.loop.run_in_executor(None, self._cleanup):
|
||||||
|
self.config.hassio_cleanup = None
|
||||||
|
|
||||||
|
def _cleanup(self):
|
||||||
|
"""Remove old image.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
old_image = "{}:{}".format(self.image, self.config.hassio_cleanup)
|
||||||
|
|
||||||
|
_LOGGER.info("Old supervisor docker found %s", old_image)
|
||||||
|
try:
|
||||||
|
self.dock.images.remove(image=old_image, force=True)
|
||||||
|
except docker.errors.DockerException as err:
|
||||||
|
_LOGGER.warning("Can't remove old image %s -> %s", old_image, err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Run docker image."""
|
"""Run docker image."""
|
||||||
raise RuntimeError("Not support on supervisor docker container!")
|
raise RuntimeError("Not support on supervisor docker container!")
|
||||||
@ -24,10 +75,6 @@ class DockerSupervisor(DockerBase):
|
|||||||
"""Stop/remove docker container."""
|
"""Stop/remove docker container."""
|
||||||
raise RuntimeError("Not support on supervisor docker container!")
|
raise RuntimeError("Not support on supervisor docker container!")
|
||||||
|
|
||||||
async def update(self, tag):
|
|
||||||
"""Update docker image."""
|
|
||||||
raise RuntimeError("Not support on supervisor docker container!")
|
|
||||||
|
|
||||||
async def remove(self):
|
async def remove(self):
|
||||||
"""Remove docker image."""
|
"""Remove docker image."""
|
||||||
raise RuntimeError("Not support on supervisor docker container!")
|
raise RuntimeError("Not support on supervisor docker container!")
|
||||||
|
@ -14,9 +14,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
TIMEOUT = 15
|
TIMEOUT = 15
|
||||||
|
|
||||||
LEVEL_POWER = 1
|
LEVEL_POWER = 1
|
||||||
LEVEL_UPDATE_SUPERVISOR = 2
|
LEVEL_UPDATE_HOST = 2
|
||||||
LEVEL_UPDATE_HOST = 4
|
LEVEL_NETWORK = 4
|
||||||
LEVEL_NETWORK = 8
|
|
||||||
|
|
||||||
|
|
||||||
class HostControll(object):
|
class HostControll(object):
|
||||||
@ -101,12 +100,3 @@ class HostControll(object):
|
|||||||
if version:
|
if version:
|
||||||
return self._send_command("host-update {}".format(version))
|
return self._send_command("host-update {}".format(version))
|
||||||
return self._send_command("host-update")
|
return self._send_command("host-update")
|
||||||
|
|
||||||
def supervisor_update(self, version=None):
|
|
||||||
"""Update the supervisor on host system.
|
|
||||||
|
|
||||||
Return a coroutine.
|
|
||||||
"""
|
|
||||||
if version:
|
|
||||||
return self._send_command("supervisor-update {}".format(version))
|
|
||||||
return self._send_command("supervisor-update")
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hassio_tag": "0.6",
|
"hassio_tag": "0.7",
|
||||||
"homeassistant_tag": "0.42.3",
|
"homeassistant_tag": "0.42.3",
|
||||||
"resinos_version": "0.3",
|
"resinos_version": "0.3",
|
||||||
"resinhup_version": "0.1"
|
"resinhup_version": "0.1"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hassio_tag": "0.6",
|
"hassio_tag": "0.7",
|
||||||
"homeassistant_tag": "0.42.3",
|
"homeassistant_tag": "0.42.3",
|
||||||
"resinos_version": "0.3",
|
"resinos_version": "0.3",
|
||||||
"resinhup_version": "0.1"
|
"resinhup_version": "0.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user