mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-09 13:09:29 +00:00
Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
65b0e17b5b | ||
![]() |
6947131b47 | ||
![]() |
914dd53da0 | ||
![]() |
58616ef686 | ||
![]() |
563e0c1e0e | ||
![]() |
437070fd7a | ||
![]() |
baa9cf451c | ||
![]() |
c2918d4519 | ||
![]() |
1efdcd4691 | ||
![]() |
2a43087ed7 | ||
![]() |
5716324934 | ||
![]() |
ae267e0380 | ||
![]() |
3918a2a228 | ||
![]() |
e375fc36d3 | ||
![]() |
f5e29b4651 | ||
![]() |
524d875516 | ||
![]() |
60bdc00ce9 | ||
![]() |
073166190f | ||
![]() |
b80e4d7d70 | ||
![]() |
cc434e27cf | ||
![]() |
8377e04b62 | ||
![]() |
0a47fb9c83 | ||
![]() |
a5d3c850e9 | ||
![]() |
d6391f62be | ||
![]() |
c6f302e448 | ||
![]() |
9706022c21 | ||
![]() |
1d858f4920 | ||
![]() |
e09ba30d46 | ||
![]() |
38ec3d14ed | ||
![]() |
8ee9380cc7 | ||
![]() |
6e74e4c008 | ||
![]() |
5ebc58851b | ||
![]() |
16b09bbfc5 | ||
![]() |
d4b5fc79f4 | ||
![]() |
e51c044ccd | ||
![]() |
d3b1ba81f7 | ||
![]() |
26f55f02c0 | ||
![]() |
8050707ff9 | ||
![]() |
46252030cf | ||
![]() |
681fa835ef | ||
![]() |
d6560eb976 | ||
![]() |
3770b307af | ||
![]() |
0dacbb31be | ||
![]() |
bbdbd756a7 | ||
![]() |
508e38e622 | ||
![]() |
ffe45d0d02 | ||
![]() |
9206d1acf8 | ||
![]() |
da867ef8ef | ||
![]() |
4826201e51 | ||
![]() |
463c97f9e7 | ||
![]() |
3983928c6c | ||
![]() |
15e626027f |
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"local": {
|
"local": {
|
||||||
"name": "Local Add-Ons",
|
"name": "Local add-ons",
|
||||||
"url": "https://home-assistant.io/hassio",
|
"url": "https://home-assistant.io/hassio",
|
||||||
"maintainer": "you"
|
"maintainer": "you"
|
||||||
},
|
},
|
||||||
"core": {
|
"core": {
|
||||||
"name": "Built-in Add-Ons",
|
"name": "Official add-ons",
|
||||||
"url": "https://home-assistant.io/addons",
|
"url": "https://home-assistant.io/addons",
|
||||||
"maintainer": "Home Assistant authors"
|
"maintainer": "Home Assistant"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,9 @@ import shutil
|
|||||||
import git
|
import git
|
||||||
|
|
||||||
from .utils import get_hash_from_repository
|
from .utils import get_hash_from_repository
|
||||||
from ..const import URL_HASSIO_ADDONS
|
from ..const import URL_HASSIO_ADDONS, ATTR_URL, ATTR_BRANCH
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..validate import RE_REPOSITORY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -22,9 +23,20 @@ class GitRepo(CoreSysAttributes):
|
|||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.repo = None
|
self.repo = None
|
||||||
self.path = path
|
self.path = path
|
||||||
self.url = url
|
|
||||||
self.lock = asyncio.Lock(loop=coresys.loop)
|
self.lock = asyncio.Lock(loop=coresys.loop)
|
||||||
|
|
||||||
|
self._data = RE_REPOSITORY.match(url).groupdict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
"""Return repository URL."""
|
||||||
|
return self._data[ATTR_URL]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def branch(self):
|
||||||
|
"""Return repository branch."""
|
||||||
|
return self._data[ATTR_BRANCH]
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Init git addon repo."""
|
"""Init git addon repo."""
|
||||||
if not self.path.is_dir():
|
if not self.path.is_dir():
|
||||||
@@ -46,12 +58,20 @@ class GitRepo(CoreSysAttributes):
|
|||||||
async def clone(self):
|
async def clone(self):
|
||||||
"""Clone git addon repo."""
|
"""Clone git addon repo."""
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
|
git_args = {
|
||||||
|
attribute: value
|
||||||
|
for attribute, value in (
|
||||||
|
('recursive', True),
|
||||||
|
('branch', self.branch)
|
||||||
|
) if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Clone addon %s repository", self.url)
|
_LOGGER.info("Clone addon %s repository", self.url)
|
||||||
self.repo = await self._loop.run_in_executor(
|
self.repo = await self._loop.run_in_executor(None, ft.partial(
|
||||||
None, ft.partial(
|
git.Repo.clone_from, self.url, str(self.path),
|
||||||
git.Repo.clone_from, self.url, str(self.path),
|
**git_args
|
||||||
recursive=True))
|
))
|
||||||
|
|
||||||
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
|
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
|
||||||
git.GitCommandError) as err:
|
git.GitCommandError) as err:
|
||||||
|
@@ -60,6 +60,7 @@ PRIVILEGED_ALL = [
|
|||||||
"NET_ADMIN",
|
"NET_ADMIN",
|
||||||
"SYS_ADMIN",
|
"SYS_ADMIN",
|
||||||
"SYS_RAWIO",
|
"SYS_RAWIO",
|
||||||
|
"IPC_LOCK",
|
||||||
"SYS_TIME",
|
"SYS_TIME",
|
||||||
"SYS_NICE"
|
"SYS_NICE"
|
||||||
]
|
]
|
||||||
|
@@ -13,7 +13,7 @@ from .proxy import APIProxy
|
|||||||
from .supervisor import APISupervisor
|
from .supervisor import APISupervisor
|
||||||
from .snapshots import APISnapshots
|
from .snapshots import APISnapshots
|
||||||
from .services import APIServices
|
from .services import APIServices
|
||||||
from .security import security_layer
|
from .security import SecurityMiddleware
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -25,16 +25,14 @@ class RestAPI(CoreSysAttributes):
|
|||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
|
self.security = SecurityMiddleware(coresys)
|
||||||
self.webapp = web.Application(
|
self.webapp = web.Application(
|
||||||
middlewares=[security_layer], loop=self._loop)
|
middlewares=[self.security.token_validation], loop=self._loop)
|
||||||
|
|
||||||
# service stuff
|
# service stuff
|
||||||
self._handler = None
|
self._handler = None
|
||||||
self.server = None
|
self.server = None
|
||||||
|
|
||||||
# middleware
|
|
||||||
self.webapp['coresys'] = coresys
|
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Register REST API Calls."""
|
"""Register REST API Calls."""
|
||||||
self._register_supervisor()
|
self._register_supervisor()
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -83,7 +83,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
if not data:
|
if not data:
|
||||||
await response.write_eof()
|
await response.write_eof()
|
||||||
break
|
break
|
||||||
response.write(data)
|
await response.write(data)
|
||||||
|
|
||||||
except aiohttp.ClientError:
|
except aiohttp.ClientError:
|
||||||
await response.write_eof()
|
await response.write_eof()
|
||||||
|
@@ -6,6 +6,7 @@ from aiohttp.web import middleware
|
|||||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||||
|
|
||||||
from ..const import HEADER_TOKEN, REQUEST_FROM
|
from ..const import HEADER_TOKEN, REQUEST_FROM
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -16,35 +17,41 @@ NO_SECURITY_CHECK = set((
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@middleware
|
class SecurityMiddleware(CoreSysAttributes):
|
||||||
async def security_layer(request, handler):
|
"""Security middleware functions."""
|
||||||
"""Check security access of this layer."""
|
|
||||||
coresys = request.app['coresys']
|
|
||||||
hassio_token = request.headers.get(HEADER_TOKEN)
|
|
||||||
|
|
||||||
# Ignore security check
|
def __init__(self, coresys):
|
||||||
for rule in NO_SECURITY_CHECK:
|
"""Initialize security middleware."""
|
||||||
if rule.match(request.path):
|
self.coresys = coresys
|
||||||
_LOGGER.debug("Passthrough %s", request.path)
|
|
||||||
|
@middleware
|
||||||
|
async def token_validation(self, request, handler):
|
||||||
|
"""Check security access of this layer."""
|
||||||
|
hassio_token = request.headers.get(HEADER_TOKEN)
|
||||||
|
|
||||||
|
# Ignore security check
|
||||||
|
for rule in NO_SECURITY_CHECK:
|
||||||
|
if rule.match(request.path):
|
||||||
|
_LOGGER.debug("Passthrough %s", request.path)
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
# Need to be removed later
|
||||||
|
if not hassio_token:
|
||||||
|
_LOGGER.warning("Invalid token for access %s", request.path)
|
||||||
|
request[REQUEST_FROM] = 'UNKNOWN'
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
# Need to be removed later
|
# Home-Assistant
|
||||||
if not hassio_token:
|
if hassio_token == self._homeassistant.uuid:
|
||||||
_LOGGER.warning("Invalid token for access %s", request.path)
|
_LOGGER.debug("%s access from Home-Assistant", request.path)
|
||||||
request[REQUEST_FROM] = 'UNKNOWN'
|
request[REQUEST_FROM] = 'homeassistant'
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
# Home-Assistant
|
# Add-on
|
||||||
if hassio_token == coresys.homeassistant.uuid:
|
addon = self._addons.from_uuid(hassio_token)
|
||||||
_LOGGER.debug("%s access from Home-Assistant", request.path)
|
if addon:
|
||||||
request[REQUEST_FROM] = 'homeassistant'
|
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
||||||
return await handler(request)
|
request[REQUEST_FROM] = addon.slug
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
# Add-on
|
raise HTTPUnauthorized()
|
||||||
addon = coresys.addons.from_uuid(hassio_token)
|
|
||||||
if addon:
|
|
||||||
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
|
||||||
request[REQUEST_FROM] = addon.slug
|
|
||||||
return await handler(request)
|
|
||||||
|
|
||||||
raise HTTPUnauthorized()
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '0.93'
|
HASSIO_VERSION = '0.98'
|
||||||
|
|
||||||
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
||||||
'hassio/{}/version.json')
|
'hassio/{}/version.json')
|
||||||
@@ -158,6 +158,7 @@ ATTR_SERVICES = 'services'
|
|||||||
ATTR_DISCOVERY = 'discovery'
|
ATTR_DISCOVERY = 'discovery'
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_PROTECTED = 'protected'
|
||||||
ATTR_CRYPTO = 'crypto'
|
ATTR_CRYPTO = 'crypto'
|
||||||
|
ATTR_BRANCH = 'branch'
|
||||||
|
|
||||||
SERVICE_MQTT = 'mqtt'
|
SERVICE_MQTT = 'mqtt'
|
||||||
|
|
||||||
|
@@ -108,10 +108,10 @@ class HassIO(CoreSysAttributes):
|
|||||||
# don't process scheduler anymore
|
# don't process scheduler anymore
|
||||||
self._scheduler.suspend = True
|
self._scheduler.suspend = True
|
||||||
|
|
||||||
# process stop tasks
|
|
||||||
self._websession.close()
|
|
||||||
self._websession_ssl.close()
|
|
||||||
|
|
||||||
# process async stop tasks
|
# process async stop tasks
|
||||||
await asyncio.wait(
|
await asyncio.wait([
|
||||||
[self._api.stop(), self._dns.stop()], loop=self._loop)
|
self._api.stop(),
|
||||||
|
self._dns.stop(),
|
||||||
|
self._websession.close(),
|
||||||
|
self._websession_ssl.close()
|
||||||
|
], loop=self._loop)
|
||||||
|
@@ -6,11 +6,11 @@ import docker
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .interface import DockerInterface
|
from .interface import DockerInterface
|
||||||
from .utils import docker_process
|
|
||||||
from ..addons.build import AddonBuild
|
from ..addons.build import AddonBuild
|
||||||
from ..const import (
|
from ..const import (
|
||||||
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
|
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
|
||||||
ENV_TIME)
|
ENV_TIME)
|
||||||
|
from ..utils import process_lock
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ class DockerAddon(DockerInterface):
|
|||||||
_LOGGER.info("Build %s:%s done", self.image, tag)
|
_LOGGER.info("Build %s:%s done", self.image, tag)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def export_image(self, path):
|
def export_image(self, path):
|
||||||
"""Export current images into a tar file."""
|
"""Export current images into a tar file."""
|
||||||
return self._loop.run_in_executor(None, self._export_image, path)
|
return self._loop.run_in_executor(None, self._export_image, path)
|
||||||
@@ -313,7 +313,7 @@ class DockerAddon(DockerInterface):
|
|||||||
_LOGGER.info("Export image %s done", self.image)
|
_LOGGER.info("Export image %s done", self.image)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def import_image(self, path, tag):
|
def import_image(self, path, tag):
|
||||||
"""Import a tar file as image."""
|
"""Import a tar file as image."""
|
||||||
return self._loop.run_in_executor(None, self._import_image, path, tag)
|
return self._loop.run_in_executor(None, self._import_image, path, tag)
|
||||||
@@ -338,7 +338,7 @@ class DockerAddon(DockerInterface):
|
|||||||
self._cleanup()
|
self._cleanup()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def write_stdin(self, data):
|
def write_stdin(self, data):
|
||||||
"""Write to add-on stdin."""
|
"""Write to add-on stdin."""
|
||||||
return self._loop.run_in_executor(None, self._write_stdin, data)
|
return self._loop.run_in_executor(None, self._write_stdin, data)
|
||||||
|
@@ -5,10 +5,10 @@ import logging
|
|||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
from .utils import docker_process
|
|
||||||
from .stats import DockerStats
|
from .stats import DockerStats
|
||||||
from ..const import LABEL_VERSION, LABEL_ARCH
|
from ..const import LABEL_VERSION, LABEL_ARCH
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..utils import process_lock
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
"""Initialize docker base wrapper."""
|
"""Initialize docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self._meta = None
|
self._meta = None
|
||||||
self.lock = asyncio.Lock(loop=self._loop)
|
self.lock = asyncio.Lock(loop=coresys.loop)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
@@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
"""Return True if a task is in progress."""
|
"""Return True if a task is in progress."""
|
||||||
return self.lock.locked()
|
return self.lock.locked()
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def install(self, tag):
|
def install(self, tag):
|
||||||
"""Pull docker image."""
|
"""Pull docker image."""
|
||||||
return self._loop.run_in_executor(None, self._install, tag)
|
return self._loop.run_in_executor(None, self._install, tag)
|
||||||
@@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def attach(self):
|
def attach(self):
|
||||||
"""Attach to running docker container."""
|
"""Attach to running docker container."""
|
||||||
return self._loop.run_in_executor(None, self._attach)
|
return self._loop.run_in_executor(None, self._attach)
|
||||||
@@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Run docker image."""
|
"""Run docker image."""
|
||||||
return self._loop.run_in_executor(None, self._run)
|
return self._loop.run_in_executor(None, self._run)
|
||||||
@@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop/remove docker container."""
|
"""Stop/remove docker container."""
|
||||||
return self._loop.run_in_executor(None, self._stop)
|
return self._loop.run_in_executor(None, self._stop)
|
||||||
@@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove docker images."""
|
"""Remove docker images."""
|
||||||
return self._loop.run_in_executor(None, self._remove)
|
return self._loop.run_in_executor(None, self._remove)
|
||||||
@@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
self._meta = None
|
self._meta = None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def update(self, tag):
|
def update(self, tag):
|
||||||
"""Update a docker image."""
|
"""Update a docker image."""
|
||||||
return self._loop.run_in_executor(None, self._update, tag)
|
return self._loop.run_in_executor(None, self._update, tag)
|
||||||
@@ -264,7 +264,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
|
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def restart(self):
|
def restart(self):
|
||||||
"""Restart docker container."""
|
"""Restart docker container."""
|
||||||
return self._loop.run_in_executor(None, self._restart)
|
return self._loop.run_in_executor(None, self._restart)
|
||||||
@@ -289,7 +289,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Check if old version exists and cleanup."""
|
"""Check if old version exists and cleanup."""
|
||||||
return self._loop.run_in_executor(None, self._cleanup)
|
return self._loop.run_in_executor(None, self._cleanup)
|
||||||
@@ -315,7 +315,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@docker_process
|
@process_lock
|
||||||
def execute_command(self, command):
|
def execute_command(self, command):
|
||||||
"""Create a temporary container and run command."""
|
"""Create a temporary container and run command."""
|
||||||
return self._loop.run_in_executor(None, self._execute_command, command)
|
return self._loop.run_in_executor(None, self._execute_command, command)
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
"""HassIO docker utilitys."""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
def docker_process(method):
|
|
||||||
"""Wrap function with only run once."""
|
|
||||||
async def wrap_api(api, *args, **kwargs):
|
|
||||||
"""Return api wrapper."""
|
|
||||||
if api.lock.locked():
|
|
||||||
_LOGGER.error(
|
|
||||||
"Can't excute %s while a task is in progress", method.__name__)
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with api.lock:
|
|
||||||
return await method(api, *args, **kwargs)
|
|
||||||
|
|
||||||
return wrap_api
|
|
@@ -16,7 +16,7 @@ from .const import (
|
|||||||
ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
|
ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
|
||||||
from .coresys import CoreSysAttributes
|
from .coresys import CoreSysAttributes
|
||||||
from .docker.homeassistant import DockerHomeAssistant
|
from .docker.homeassistant import DockerHomeAssistant
|
||||||
from .utils import convert_to_ascii
|
from .utils import convert_to_ascii, process_lock
|
||||||
from .utils.json import JsonConfig
|
from .utils.json import JsonConfig
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
from .validate import SCHEMA_HASS_CONFIG
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.instance = DockerHomeAssistant(coresys)
|
self.instance = DockerHomeAssistant(coresys)
|
||||||
|
self.lock = asyncio.Lock(loop=coresys.loop)
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Prepare HomeAssistant object."""
|
"""Prepare HomeAssistant object."""
|
||||||
@@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return a UUID of this HomeAssistant."""
|
"""Return a UUID of this HomeAssistant."""
|
||||||
return self._data[ATTR_UUID]
|
return self._data[ATTR_UUID]
|
||||||
|
|
||||||
|
@process_lock
|
||||||
async def install_landingpage(self):
|
async def install_landingpage(self):
|
||||||
"""Install a landingpage."""
|
"""Install a landingpage."""
|
||||||
_LOGGER.info("Setup HomeAssistant landingpage")
|
_LOGGER.info("Setup HomeAssistant landingpage")
|
||||||
@@ -172,8 +174,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
await asyncio.sleep(60, loop=self._loop)
|
await asyncio.sleep(60, loop=self._loop)
|
||||||
|
|
||||||
# Run landingpage after installation
|
# Run landingpage after installation
|
||||||
await self.start()
|
await self._start()
|
||||||
|
|
||||||
|
@process_lock
|
||||||
async def install(self):
|
async def install(self):
|
||||||
"""Install a landingpage."""
|
"""Install a landingpage."""
|
||||||
_LOGGER.info("Setup HomeAssistant")
|
_LOGGER.info("Setup HomeAssistant")
|
||||||
@@ -191,9 +194,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
# finishing
|
# finishing
|
||||||
_LOGGER.info("HomeAssistant docker now installed")
|
_LOGGER.info("HomeAssistant docker now installed")
|
||||||
if self.boot:
|
if self.boot:
|
||||||
await self.start()
|
await self._start()
|
||||||
await self.instance.cleanup()
|
await self.instance.cleanup()
|
||||||
|
|
||||||
|
@process_lock
|
||||||
async def update(self, version=None):
|
async def update(self, version=None):
|
||||||
"""Update HomeAssistant version."""
|
"""Update HomeAssistant version."""
|
||||||
version = version or self.last_version
|
version = version or self.last_version
|
||||||
@@ -208,8 +212,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
return await self.instance.update(version)
|
return await self.instance.update(version)
|
||||||
finally:
|
finally:
|
||||||
if running:
|
if running:
|
||||||
await self.start()
|
await self._start()
|
||||||
|
|
||||||
|
async def _start(self):
|
||||||
|
"""Start HomeAssistant docker & wait."""
|
||||||
|
if await self.instance.run():
|
||||||
|
await self._block_till_run()
|
||||||
|
|
||||||
|
@process_lock
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Run HomeAssistant docker."""
|
"""Run HomeAssistant docker."""
|
||||||
if not await self.instance.run():
|
if not await self.instance.run():
|
||||||
@@ -224,6 +234,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
"""
|
"""
|
||||||
return self.instance.stop()
|
return self.instance.stop()
|
||||||
|
|
||||||
|
@process_lock
|
||||||
async def restart(self):
|
async def restart(self):
|
||||||
"""Restart HomeAssistant docker."""
|
"""Restart HomeAssistant docker."""
|
||||||
if not await self.instance.restart():
|
if not await self.instance.restart():
|
||||||
@@ -262,7 +273,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def in_progress(self):
|
def in_progress(self):
|
||||||
"""Return True if a task is in progress."""
|
"""Return True if a task is in progress."""
|
||||||
return self.instance.in_progress
|
return self.instance.in_progress or self.lock.locked()
|
||||||
|
|
||||||
async def check_config(self):
|
async def check_config(self):
|
||||||
"""Run homeassistant config check."""
|
"""Run homeassistant config check."""
|
||||||
|
@@ -173,14 +173,17 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
if addon and addon.is_installed:
|
if addon and addon.is_installed:
|
||||||
addon_list.append(addon)
|
addon_list.append(addon)
|
||||||
continue
|
continue
|
||||||
_LOGGER.warning("Add-on %s not found", addon_slug)
|
_LOGGER.warning(
|
||||||
|
"Add-on %s not found/installed", addon_slug)
|
||||||
|
|
||||||
_LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
|
if addon_list:
|
||||||
await snapshot.store_addons(addon_list)
|
_LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
|
||||||
|
await snapshot.store_addons(addon_list)
|
||||||
|
|
||||||
# snapshot folders
|
# Snapshot folders
|
||||||
_LOGGER.info("Snapshot %s store folders", snapshot.slug)
|
if folders:
|
||||||
await snapshot.store_folders(folders)
|
_LOGGER.info("Snapshot %s store folders", snapshot.slug)
|
||||||
|
await snapshot.store_folders(folders)
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Snapshot %s error", snapshot.slug)
|
_LOGGER.exception("Snapshot %s error", snapshot.slug)
|
||||||
|
@@ -151,7 +151,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
def _encrypt_data(self, data):
|
def _encrypt_data(self, data):
|
||||||
"""Make data secure."""
|
"""Make data secure."""
|
||||||
if not self._key:
|
if not self._key or data is None:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return b64encode(
|
return b64encode(
|
||||||
@@ -159,7 +159,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
def _decrypt_data(self, data):
|
def _decrypt_data(self, data):
|
||||||
"""Make data readable."""
|
"""Make data readable."""
|
||||||
if not self._key:
|
if not self._key or data is None:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return Padding.unpad(
|
return Padding.unpad(
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
"""Tools file for HassIO."""
|
"""Tools file for HassIO."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
||||||
|
|
||||||
|
|
||||||
@@ -10,6 +12,21 @@ def convert_to_ascii(raw):
|
|||||||
return RE_STRING.sub("", raw.decode())
|
return RE_STRING.sub("", raw.decode())
|
||||||
|
|
||||||
|
|
||||||
|
def process_lock(method):
|
||||||
|
"""Wrap function with only run once."""
|
||||||
|
async def wrap_api(api, *args, **kwargs):
|
||||||
|
"""Return api wrapper."""
|
||||||
|
if api.lock.locked():
|
||||||
|
_LOGGER.error(
|
||||||
|
"Can't excute %s while a task is in progress", method.__name__)
|
||||||
|
return False
|
||||||
|
|
||||||
|
async with api.lock:
|
||||||
|
return await method(api, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrap_api
|
||||||
|
|
||||||
|
|
||||||
class AsyncThrottle(object):
|
class AsyncThrottle(object):
|
||||||
"""
|
"""
|
||||||
Decorator that prevents a function from being called more than once every
|
Decorator that prevents a function from being called more than once every
|
||||||
|
@@ -12,7 +12,7 @@ UTC = pytz.utc
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
FREEGEOIP_URL = "https://freegeoip.io/json/"
|
FREEGEOIP_URL = "https://freegeoip.net/json/"
|
||||||
|
|
||||||
# Copyright (c) Django Software Foundation and individual contributors.
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
"""Validate functions."""
|
"""Validate functions."""
|
||||||
import uuid
|
import uuid
|
||||||
|
import re
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import pytz
|
import pytz
|
||||||
@@ -11,13 +12,29 @@ from .const import (
|
|||||||
ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID)
|
ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID)
|
||||||
|
|
||||||
|
|
||||||
|
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
||||||
|
|
||||||
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||||
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
|
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
|
||||||
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
||||||
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_repository(repository):
|
||||||
|
"""Validate a valide repository."""
|
||||||
|
data = RE_REPOSITORY.match(repository)
|
||||||
|
if not data:
|
||||||
|
raise vol.Invalid("No valid repository format!")
|
||||||
|
|
||||||
|
# Validate URL
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
vol.Url()(data.group('url'))
|
||||||
|
|
||||||
|
return repository
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
REPOSITORIES = vol.All([vol.Url()], vol.Unique())
|
REPOSITORIES = vol.All([validate_repository], vol.Unique())
|
||||||
|
|
||||||
|
|
||||||
def validate_timezone(timezone):
|
def validate_timezone(timezone):
|
||||||
|
Submodule home-assistant-polymer updated: 5f5ac3834d...2c79094fb4
4
setup.py
4
setup.py
@@ -41,8 +41,8 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'async_timeout==2.0.0',
|
'async_timeout==2.0.0',
|
||||||
'aiohttp==2.3.10',
|
'aiohttp==3.0.9',
|
||||||
'docker==3.1.0',
|
'docker==3.1.1',
|
||||||
'colorlog==3.1.2',
|
'colorlog==3.1.2',
|
||||||
'voluptuous==0.11.1',
|
'voluptuous==0.11.1',
|
||||||
'gitpython==2.1.8',
|
'gitpython==2.1.8',
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"hassio": "0.93",
|
"hassio": "0.98",
|
||||||
"homeassistant": "0.63.3",
|
"homeassistant": "0.65.4",
|
||||||
"resinos": "1.1",
|
"resinos": "1.3",
|
||||||
"resinhup": "0.3",
|
"resinhup": "0.3",
|
||||||
"generic": "0.3",
|
"generic": "0.3",
|
||||||
"cluster": "0.1"
|
"cluster": "0.1"
|
||||||
|
Reference in New Issue
Block a user