Merge pull request #706 from home-assistant/dev

Release 131
This commit is contained in:
Pascal Vizeli 2018-09-18 21:19:33 +02:00 committed by GitHub
commit ccff0f5b9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 322 additions and 218 deletions

View File

@ -7,3 +7,7 @@
# Temporary files # Temporary files
**/__pycache__ **/__pycache__
# virtualenv
venv/
ENV/

2
API.md
View File

@ -472,6 +472,7 @@ Get all available addons.
"options": "{}", "options": "{}",
"network": "{}|null", "network": "{}|null",
"host_network": "bool", "host_network": "bool",
"host_pid": "bool",
"host_ipc": "bool", "host_ipc": "bool",
"host_dbus": "bool", "host_dbus": "bool",
"privileged": ["NET_ADMIN", "SYS_ADMIN"], "privileged": ["NET_ADMIN", "SYS_ADMIN"],
@ -482,6 +483,7 @@ Get all available addons.
"logo": "bool", "logo": "bool",
"changelog": "bool", "changelog": "bool",
"hassio_api": "bool", "hassio_api": "bool",
"hassio_role": "default|homeassistant|manager|admin",
"homeassistant_api": "bool", "homeassistant_api": "bool",
"full_access": "bool", "full_access": "bool",
"protected": "bool", "protected": "bool",

View File

@ -1 +1 @@
"""Init file for HassIO.""" """Init file for Hass.io."""

View File

@ -1,4 +1,4 @@
"""Main file for HassIO.""" """Main file for Hass.io."""
import asyncio import asyncio
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import logging import logging
@ -31,7 +31,7 @@ if __name__ == "__main__":
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
loop.set_default_executor(executor) loop.set_default_executor(executor)
_LOGGER.info("Initialize Hassio setup") _LOGGER.info("Initialize Hass.io setup")
coresys = bootstrap.initialize_coresys(loop) coresys = bootstrap.initialize_coresys(loop)
bootstrap.migrate_system_env(coresys) bootstrap.migrate_system_env(coresys)
@ -43,13 +43,13 @@ if __name__ == "__main__":
loop.call_soon_threadsafe(bootstrap.reg_signal, loop) loop.call_soon_threadsafe(bootstrap.reg_signal, loop)
try: try:
_LOGGER.info("Run HassIO") _LOGGER.info("Run Hass.io")
loop.run_forever() loop.run_forever()
finally: finally:
_LOGGER.info("Stopping HassIO") _LOGGER.info("Stopping Hass.io")
loop.run_until_complete(coresys.core.stop()) loop.run_until_complete(coresys.core.stop())
executor.shutdown(wait=False) executor.shutdown(wait=False)
loop.close() loop.close()
_LOGGER.info("Close Hassio") _LOGGER.info("Close Hass.io")
sys.exit(0) sys.exit(0)

View File

@ -26,7 +26,7 @@ from ..const import (
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT) SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..docker.addon import DockerAddon from ..docker.addon import DockerAddon
@ -307,6 +307,11 @@ class Addon(CoreSysAttributes):
"""Return True if addon run on host network.""" """Return True if addon run on host network."""
return self._mesh[ATTR_HOST_NETWORK] return self._mesh[ATTR_HOST_NETWORK]
@property
def host_pid(self):
"""Return True if addon run on host PID namespace."""
return self._mesh[ATTR_HOST_PID]
@property @property
def host_ipc(self): def host_ipc(self):
"""Return True if addon run on host IPC namespace.""" """Return True if addon run on host IPC namespace."""
@ -371,6 +376,11 @@ class Addon(CoreSysAttributes):
"""Return True if the add-on access to Home-Assistant api proxy.""" """Return True if the add-on access to Home-Assistant api proxy."""
return self._mesh[ATTR_HOMEASSISTANT_API] return self._mesh[ATTR_HOMEASSISTANT_API]
@property
def hassio_role(self):
"""Return Hass.io role for API."""
return self._mesh[ATTR_HASSIO_ROLE]
@property @property
def with_stdin(self): def with_stdin(self):
"""Return True if the add-on access use stdin input.""" """Return True if the add-on access use stdin input."""
@ -603,7 +613,7 @@ class Addon(CoreSysAttributes):
return vol.Schema(dict) return vol.Schema(dict)
return vol.Schema(vol.All(dict, validate_options(raw_schema))) return vol.Schema(vol.All(dict, validate_options(raw_schema)))
def test_udpate_schema(self): def test_update_schema(self):
"""Check if the exists config valid after update.""" """Check if the exists config valid after update."""
if not self.is_installed or self.is_detached: if not self.is_installed or self.is_detached:
return True return True

View File

@ -6,7 +6,8 @@ import re
from ..const import ( from ..const import (
SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN, SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN,
PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO) PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE,
ROLE_ADMIN, ROLE_MANAGER)
RE_SHA1 = re.compile(r"[a-f0-9]{8}") RE_SHA1 = re.compile(r"[a-f0-9]{8}")
@ -33,13 +34,23 @@ def rating_security(addon):
# Privileged options # Privileged options
if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
PRIVILEGED_SYS_RAWIO): PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
rating += -1 rating += -1
# API Hass.io role
if addon.hassio_role == ROLE_MANAGER:
rating += -1
elif addon.hassio_role == ROLE_ADMIN:
rating += -2
# Not secure Networking # Not secure Networking
if addon.host_network: if addon.host_network:
rating += -1 rating += -1
# Insecure PID namespace
if addon.host_pid:
rating += -2
# Full Access # Full Access
if addon.with_full_access: if addon.with_full_access:
rating += -2 rating += -2

View File

@ -19,10 +19,11 @@ from ..const import (
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE, PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
PRIVILEGED_SYS_RESOURCE) PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE,
ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN)
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -69,6 +70,14 @@ PRIVILEGED_ALL = [
PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_TIME,
PRIVILEGED_SYS_NICE, PRIVILEGED_SYS_NICE,
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_RESOURCE,
PRIVILEGED_SYS_PTRACE,
]
ROLE_ALL = [
ROLE_DEFAULT,
ROLE_HOMEASSISTANT,
ROLE_MANAGER,
ROLE_ADMIN,
] ]
BASE_IMAGE = { BASE_IMAGE = {
@ -104,6 +113,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Optional(ATTR_WEBUI): vol.Optional(ATTR_WEBUI):
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"), vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_DBUS, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_DBUS, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")], vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
@ -119,6 +129,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Optional(ATTR_GPIO, default=False): vol.Boolean(), vol.Optional(ATTR_GPIO, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(), vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(),
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(), vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(), vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
vol.Optional(ATTR_STDIN, default=False): vol.Boolean(), vol.Optional(ATTR_STDIN, default=False): vol.Boolean(),
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(), vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),

View File

@ -19,9 +19,9 @@ from ..const import (
ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX, ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX,
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES, ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, ATTR_HASSIO_ROLE,
REQUEST_FROM) CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import DOCKER_PORTS, ALSA_DEVICE from ..validate import DOCKER_PORTS, ALSA_DEVICE
from ..exceptions import APINotSupportedError from ..exceptions import APINotSupportedError
@ -140,6 +140,7 @@ class APIAddons(CoreSysAttributes):
ATTR_BUILD: addon.need_build, ATTR_BUILD: addon.need_build,
ATTR_NETWORK: addon.ports, ATTR_NETWORK: addon.ports,
ATTR_HOST_NETWORK: addon.host_network, ATTR_HOST_NETWORK: addon.host_network,
ATTR_HOST_PID: addon.host_pid,
ATTR_HOST_IPC: addon.host_ipc, ATTR_HOST_IPC: addon.host_ipc,
ATTR_HOST_DBUS: addon.host_dbus, ATTR_HOST_DBUS: addon.host_dbus,
ATTR_PRIVILEGED: addon.privileged, ATTR_PRIVILEGED: addon.privileged,
@ -152,6 +153,7 @@ class APIAddons(CoreSysAttributes):
ATTR_WEBUI: addon.webui, ATTR_WEBUI: addon.webui,
ATTR_STDIN: addon.with_stdin, ATTR_STDIN: addon.with_stdin,
ATTR_HASSIO_API: addon.access_hassio_api, ATTR_HASSIO_API: addon.access_hassio_api,
ATTR_HASSIO_ROLE: addon.hassio_role,
ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api, ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api,
ATTR_GPIO: addon.with_gpio, ATTR_GPIO: addon.with_gpio,
ATTR_DEVICETREE: addon.with_devicetree, ATTR_DEVICETREE: addon.with_devicetree,
@ -196,6 +198,7 @@ class APIAddons(CoreSysAttributes):
addon = self._extract_addon(request) addon = self._extract_addon(request)
# Have Access # Have Access
# REMOVE: don't needed anymore
if addon.slug == request[REQUEST_FROM]: if addon.slug == request[REQUEST_FROM]:
_LOGGER.error("Can't self modify his security!") _LOGGER.error("Can't self modify his security!")
raise APINotSupportedError() raise APINotSupportedError()

View File

@ -25,7 +25,7 @@ class APIProxy(CoreSysAttributes):
hassio_token = request.headers.get(HEADER_HA_ACCESS) hassio_token = request.headers.get(HEADER_HA_ACCESS)
addon = self.sys_addons.from_token(hassio_token) addon = self.sys_addons.from_token(hassio_token)
# Need removed with 131 # REMOVE 132
if not addon: if not addon:
addon = self.sys_addons.from_uuid(hassio_token) addon = self.sys_addons.from_uuid(hassio_token)
@ -184,7 +184,7 @@ class APIProxy(CoreSysAttributes):
response.get('access_token')) response.get('access_token'))
addon = self.sys_addons.from_token(hassio_token) addon = self.sys_addons.from_token(hassio_token)
# Need removed with 131 # REMOVE 132
if not addon: if not addon:
addon = self.sys_addons.from_uuid(hassio_token) addon = self.sys_addons.from_uuid(hassio_token)

View File

@ -5,27 +5,61 @@ import re
from aiohttp.web import middleware from aiohttp.web import middleware
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
from ..const import HEADER_TOKEN, REQUEST_FROM from ..const import (
HEADER_TOKEN, REQUEST_FROM, ROLE_ADMIN, ROLE_DEFAULT, ROLE_HOMEASSISTANT,
ROLE_MANAGER)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Free to call or have own security concepts
NO_SECURITY_CHECK = re.compile( NO_SECURITY_CHECK = re.compile(
r"^(?:" r"^(?:"
r"|/homeassistant/api/.*$" r"|/homeassistant/api/.*"
r"|/homeassistant/websocket$" r"|/homeassistant/websocket"
r"|/supervisor/ping$" r"|/supervisor/ping"
r"|/services.*"
r")$" r")$"
) )
# Can called by every add-on
ADDONS_API_BYPASS = re.compile( ADDONS_API_BYPASS = re.compile(
r"^(?:" r"^(?:"
r"|/homeassistant/info$" r"|/homeassistant/info"
r"|/supervisor/info$" r"|/supervisor/info"
r"|/addons(?:/self/[^/]+)?$" r"|/addons(?:/self/(?!security)[^/]+)?"
r")$" r")$"
) )
# Policy role add-on API access
ADDONS_ROLE_ACCESS = {
ROLE_DEFAULT: re.compile(
r"^(?:"
r"|/[^/]+/info"
r")$"
),
ROLE_HOMEASSISTANT: re.compile(
r"^(?:"
r"|/homeassistant/.+"
r")$"
),
ROLE_MANAGER: re.compile(
r"^(?:"
r"|/homeassistant/.+"
r"|/host/.+"
r"|/hardware/.+"
r"|/hassos/.+"
r"|/supervisor/.+"
r"|/addons/.+/(?!security|options).+"
r"|/addons(?:/self/(?!security).+)"
r"|/snapshots.*"
r")$"
),
ROLE_ADMIN: re.compile(
r".+"
),
}
class SecurityMiddleware(CoreSysAttributes): class SecurityMiddleware(CoreSysAttributes):
"""Security middleware functions.""" """Security middleware functions."""
@ -66,17 +100,22 @@ class SecurityMiddleware(CoreSysAttributes):
addon = None addon = None
if hassio_token and not request_from: if hassio_token and not request_from:
addon = self.sys_addons.from_token(hassio_token) addon = self.sys_addons.from_token(hassio_token)
# Need removed with 131 # REMOVE 132
if not addon: if not addon:
addon = self.sys_addons.from_uuid(hassio_token) addon = self.sys_addons.from_uuid(hassio_token)
# Check Add-on API access # Check Add-on API access
if addon and addon.access_hassio_api: if addon and ADDONS_API_BYPASS.match(request.path):
_LOGGER.info("%s access from %s", request.path, addon.slug)
request_from = addon.slug
elif addon and ADDONS_API_BYPASS.match(request.path):
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug) _LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
request_from = addon.slug request_from = addon.slug
elif addon and addon.access_hassio_api:
# Check Role
if ADDONS_ROLE_ACCESS[addon.hassio_role].match(request.path):
_LOGGER.info("%s access from %s", request.path, addon.slug)
request_from = addon.slug
else:
_LOGGER.warning("%s no role for %s", request.path, addon.slug)
request_from = addon.slug # REMOVE: 132
if request_from: if request_from:
request[REQUEST_FROM] = request_from request[REQUEST_FROM] = request_from

View File

@ -1,4 +1,4 @@
"""Bootstrap HassIO.""" """Bootstrap Hass.io."""
import logging import logging
import os import os
import signal import signal
@ -62,55 +62,55 @@ def initialize_coresys(loop):
def initialize_system_data(coresys): def initialize_system_data(coresys):
"""Setup default config and create folders.""" """Set up the default configuration and create folders."""
config = coresys.config config = coresys.config
# homeassistant config folder # Home Assistant configuration folder
if not config.path_homeassistant.is_dir(): if not config.path_homeassistant.is_dir():
_LOGGER.info( _LOGGER.info(
"Create Home-Assistant config folder %s", "Create Home Assistant configuration folder %s",
config.path_homeassistant) config.path_homeassistant)
config.path_homeassistant.mkdir() config.path_homeassistant.mkdir()
# hassio ssl folder # hassio ssl folder
if not config.path_ssl.is_dir(): if not config.path_ssl.is_dir():
_LOGGER.info("Create hassio ssl folder %s", config.path_ssl) _LOGGER.info("Create Hass.io SSL/TLS folder %s", config.path_ssl)
config.path_ssl.mkdir() config.path_ssl.mkdir()
# hassio addon data folder # hassio addon data folder
if not config.path_addons_data.is_dir(): if not config.path_addons_data.is_dir():
_LOGGER.info( _LOGGER.info(
"Create hassio addon data folder %s", config.path_addons_data) "Create Hass.io Add-on data folder %s", config.path_addons_data)
config.path_addons_data.mkdir(parents=True) config.path_addons_data.mkdir(parents=True)
if not config.path_addons_local.is_dir(): if not config.path_addons_local.is_dir():
_LOGGER.info("Create hassio addon local repository folder %s", _LOGGER.info("Create Hass.io Add-on local repository folder %s",
config.path_addons_local) config.path_addons_local)
config.path_addons_local.mkdir(parents=True) config.path_addons_local.mkdir(parents=True)
if not config.path_addons_git.is_dir(): if not config.path_addons_git.is_dir():
_LOGGER.info("Create hassio addon git repositories folder %s", _LOGGER.info("Create Hass.io Add-on git repositories folder %s",
config.path_addons_git) config.path_addons_git)
config.path_addons_git.mkdir(parents=True) config.path_addons_git.mkdir(parents=True)
# hassio tmp folder # hassio tmp folder
if not config.path_tmp.is_dir(): if not config.path_tmp.is_dir():
_LOGGER.info("Create hassio temp folder %s", config.path_tmp) _LOGGER.info("Create Hass.io temp folder %s", config.path_tmp)
config.path_tmp.mkdir(parents=True) config.path_tmp.mkdir(parents=True)
# hassio backup folder # hassio backup folder
if not config.path_backup.is_dir(): if not config.path_backup.is_dir():
_LOGGER.info("Create hassio backup folder %s", config.path_backup) _LOGGER.info("Create Hass.io backup folder %s", config.path_backup)
config.path_backup.mkdir() config.path_backup.mkdir()
# share folder # share folder
if not config.path_share.is_dir(): if not config.path_share.is_dir():
_LOGGER.info("Create hassio share folder %s", config.path_share) _LOGGER.info("Create Hass.io share folder %s", config.path_share)
config.path_share.mkdir() config.path_share.mkdir()
# apparmor folder # apparmor folder
if not config.path_apparmor.is_dir(): if not config.path_apparmor.is_dir():
_LOGGER.info("Create hassio apparmor folder %s", config.path_apparmor) _LOGGER.info("Create Hass.io Apparmor folder %s", config.path_apparmor)
config.path_apparmor.mkdir() config.path_apparmor.mkdir()
return config return config
@ -126,7 +126,7 @@ def migrate_system_env(coresys):
try: try:
old_build.rmdir() old_build.rmdir()
except OSError: except OSError:
_LOGGER.warning("Can't cleanup old addons build dir.") _LOGGER.warning("Can't cleanup old Add-on build directory")
def initialize_logging(): def initialize_logging():
@ -166,24 +166,24 @@ def check_environment():
# check docker socket # check docker socket
if not SOCKET_DOCKER.is_socket(): if not SOCKET_DOCKER.is_socket():
_LOGGER.fatal("Can't find docker socket!") _LOGGER.fatal("Can't find Docker socket!")
return False return False
# check socat exec # check socat exec
if not shutil.which('socat'): if not shutil.which('socat'):
_LOGGER.fatal("Can't find socat program!") _LOGGER.fatal("Can't find socat!")
return False return False
# check socat exec # check socat exec
if not shutil.which('gdbus'): if not shutil.which('gdbus'):
_LOGGER.fatal("Can't find gdbus program!") _LOGGER.fatal("Can't find gdbus!")
return False return False
return True return True
def reg_signal(loop): def reg_signal(loop):
"""Register SIGTERM, SIGKILL to stop system.""" """Register SIGTERM and SIGKILL to stop system."""
try: try:
loop.add_signal_handler( loop.add_signal_handler(
signal.SIGTERM, lambda: loop.call_soon(loop.stop)) signal.SIGTERM, lambda: loop.call_soon(loop.stop))

View File

@ -1,4 +1,4 @@
"""Bootstrap HassIO.""" """Bootstrap Hass.io."""
from datetime import datetime from datetime import datetime
import logging import logging
import os import os
@ -16,7 +16,7 @@ from .validate import SCHEMA_HASSIO_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HOMEASSISTANT_CONFIG = PurePath("homeassistant") HOMEASSISTANT_CONFIG = PurePath('homeassistant')
HASSIO_SSL = PurePath("ssl") HASSIO_SSL = PurePath("ssl")
@ -93,17 +93,17 @@ class CoreConfig(JsonConfig):
@property @property
def path_hassio(self): def path_hassio(self):
"""Return hassio data path.""" """Return Hass.io data path."""
return HASSIO_DATA return HASSIO_DATA
@property @property
def path_extern_hassio(self): def path_extern_hassio(self):
"""Return hassio data path extern for docker.""" """Return Hass.io data path external for Docker."""
return PurePath(os.environ['SUPERVISOR_SHARE']) return PurePath(os.environ['SUPERVISOR_SHARE'])
@property @property
def path_extern_homeassistant(self): def path_extern_homeassistant(self):
"""Return config path extern for docker.""" """Return config path external for Docker."""
return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG)) return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG))
@property @property
@ -113,7 +113,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_ssl(self): def path_extern_ssl(self):
"""Return SSL path extern for docker.""" """Return SSL path external for Docker."""
return str(PurePath(self.path_extern_hassio, HASSIO_SSL)) return str(PurePath(self.path_extern_hassio, HASSIO_SSL))
@property @property
@ -123,42 +123,42 @@ class CoreConfig(JsonConfig):
@property @property
def path_addons_core(self): def path_addons_core(self):
"""Return git path for core addons.""" """Return git path for core Add-ons."""
return Path(HASSIO_DATA, ADDONS_CORE) return Path(HASSIO_DATA, ADDONS_CORE)
@property @property
def path_addons_git(self): def path_addons_git(self):
"""Return path for git addons.""" """Return path for Git Add-on."""
return Path(HASSIO_DATA, ADDONS_GIT) return Path(HASSIO_DATA, ADDONS_GIT)
@property @property
def path_addons_local(self): def path_addons_local(self):
"""Return path for customs addons.""" """Return path for custom Add-ons."""
return Path(HASSIO_DATA, ADDONS_LOCAL) return Path(HASSIO_DATA, ADDONS_LOCAL)
@property @property
def path_extern_addons_local(self): def path_extern_addons_local(self):
"""Return path for customs addons.""" """Return path for custom Add-ons."""
return PurePath(self.path_extern_hassio, ADDONS_LOCAL) return PurePath(self.path_extern_hassio, ADDONS_LOCAL)
@property @property
def path_addons_data(self): def path_addons_data(self):
"""Return root addon data folder.""" """Return root Add-on data folder."""
return Path(HASSIO_DATA, ADDONS_DATA) return Path(HASSIO_DATA, ADDONS_DATA)
@property @property
def path_extern_addons_data(self): def path_extern_addons_data(self):
"""Return root addon data folder extern for docker.""" """Return root add-on data folder external for Docker."""
return PurePath(self.path_extern_hassio, ADDONS_DATA) return PurePath(self.path_extern_hassio, ADDONS_DATA)
@property @property
def path_tmp(self): def path_tmp(self):
"""Return hass.io temp folder.""" """Return Hass.io temp folder."""
return Path(HASSIO_DATA, TMP_DATA) return Path(HASSIO_DATA, TMP_DATA)
@property @property
def path_extern_tmp(self): def path_extern_tmp(self):
"""Return hass.io temp folder for docker.""" """Return Hass.io temp folder for Docker."""
return PurePath(self.path_extern_hassio, TMP_DATA) return PurePath(self.path_extern_hassio, TMP_DATA)
@property @property
@ -168,7 +168,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_backup(self): def path_extern_backup(self):
"""Return root backup data folder extern for docker.""" """Return root backup data folder external for Docker."""
return PurePath(self.path_extern_hassio, BACKUP_DATA) return PurePath(self.path_extern_hassio, BACKUP_DATA)
@property @property
@ -178,17 +178,17 @@ class CoreConfig(JsonConfig):
@property @property
def path_apparmor(self): def path_apparmor(self):
"""Return root apparmor profile folder.""" """Return root Apparmor profile folder."""
return Path(HASSIO_DATA, APPARMOR_DATA) return Path(HASSIO_DATA, APPARMOR_DATA)
@property @property
def path_extern_share(self): def path_extern_share(self):
"""Return root share data folder extern for docker.""" """Return root share data folder external for Docker."""
return PurePath(self.path_extern_hassio, SHARE_DATA) return PurePath(self.path_extern_hassio, SHARE_DATA)
@property @property
def addons_repositories(self): def addons_repositories(self):
"""Return list of addons custom 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):

View File

@ -1,8 +1,8 @@
"""Const file for HassIO.""" """Constants file for Hass.io."""
from pathlib import Path from pathlib import Path
from ipaddress import ip_network from ipaddress import ip_network
HASSIO_VERSION = '130' HASSIO_VERSION = '131'
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
URL_HASSIO_VERSION = \ URL_HASSIO_VERSION = \
@ -114,6 +114,7 @@ ATTR_BUILD = 'build'
ATTR_DEVICES = 'devices' ATTR_DEVICES = 'devices'
ATTR_ENVIRONMENT = 'environment' ATTR_ENVIRONMENT = 'environment'
ATTR_HOST_NETWORK = 'host_network' ATTR_HOST_NETWORK = 'host_network'
ATTR_HOST_PID = 'host_pid'
ATTR_HOST_IPC = 'host_ipc' ATTR_HOST_IPC = 'host_ipc'
ATTR_HOST_DBUS = 'host_dbus' ATTR_HOST_DBUS = 'host_dbus'
ATTR_NETWORK = 'network' ATTR_NETWORK = 'network'
@ -183,6 +184,7 @@ ATTR_DOCKER_API = 'docker_api'
ATTR_FULL_ACCESS = 'full_access' ATTR_FULL_ACCESS = 'full_access'
ATTR_PROTECTED = 'protected' ATTR_PROTECTED = 'protected'
ATTR_RATING = 'rating' ATTR_RATING = 'rating'
ATTR_HASSIO_ROLE = 'hassio_role'
SERVICE_MQTT = 'mqtt' SERVICE_MQTT = 'mqtt'
@ -238,9 +240,15 @@ PRIVILEGED_IPC_LOCK = 'IPC_LOCK'
PRIVILEGED_SYS_TIME = 'SYS_TIME' PRIVILEGED_SYS_TIME = 'SYS_TIME'
PRIVILEGED_SYS_NICE = 'SYS_NICE' PRIVILEGED_SYS_NICE = 'SYS_NICE'
PRIVILEGED_SYS_RESOURCE = 'SYS_RESOURCE' PRIVILEGED_SYS_RESOURCE = 'SYS_RESOURCE'
PRIVILEGED_SYS_PTRACE = 'SYS_PTRACE'
FEATURES_SHUTDOWN = 'shutdown' FEATURES_SHUTDOWN = 'shutdown'
FEATURES_REBOOT = 'reboot' FEATURES_REBOOT = 'reboot'
FEATURES_HASSOS = 'hassos' FEATURES_HASSOS = 'hassos'
FEATURES_HOSTNAME = 'hostname' FEATURES_HOSTNAME = 'hostname'
FEATURES_SERVICES = 'services' FEATURES_SERVICES = 'services'
ROLE_DEFAULT = 'default'
ROLE_HOMEASSISTANT = 'homeassistant'
ROLE_MANAGER = 'manager'
ROLE_ADMIN = 'admin'

View File

@ -1,4 +1,4 @@
"""Main file for HassIO.""" """Main file for Hass.io."""
from contextlib import suppress from contextlib import suppress
import asyncio import asyncio
import logging import logging
@ -14,10 +14,10 @@ _LOGGER = logging.getLogger(__name__)
class HassIO(CoreSysAttributes): class HassIO(CoreSysAttributes):
"""Main object of hassio.""" """Main object of Hass.io."""
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize hassio object.""" """Initialize Hass.io object."""
self.coresys = coresys self.coresys = coresys
async def setup(self): async def setup(self):
@ -56,7 +56,7 @@ class HassIO(CoreSysAttributes):
self.sys_create_task(self.sys_dns.start()) self.sys_create_task(self.sys_dns.start())
async def start(self): async def start(self):
"""Start HassIO orchestration.""" """Start Hass.io orchestration."""
# on release channel, try update itself # on release channel, try update itself
# on dev mode, only read new versions # on dev mode, only read new versions
if not self.sys_dev and self.sys_supervisor.need_update: if not self.sys_dev and self.sys_supervisor.need_update:

View File

@ -1,5 +1,4 @@
"""Handle core shared data.""" """Handle core shared data."""
import aiohttp import aiohttp
from .const import CHANNEL_DEV from .const import CHANNEL_DEV
@ -49,21 +48,21 @@ class CoreSys:
@property @property
def arch(self): def arch(self):
"""Return running arch of hass.io system.""" """Return running arch of the Hass.io system."""
if self._supervisor: if self._supervisor:
return self._supervisor.arch return self._supervisor.arch
return None return None
@property @property
def machine(self): def machine(self):
"""Return running machine type of hass.io system.""" """Return running machine type of the Hass.io system."""
if self._homeassistant: if self._homeassistant:
return self._homeassistant.machine return self._homeassistant.machine
return None return None
@property @property
def dev(self): def dev(self):
"""Return True if we run dev modus.""" """Return True if we run dev mode."""
return self._updater.channel == CHANNEL_DEV return self._updater.channel == CHANNEL_DEV
@property @property
@ -118,9 +117,9 @@ class CoreSys:
@core.setter @core.setter
def core(self, value): def core(self, value):
"""Set a HassIO object.""" """Set a Hass.io object."""
if self._core: if self._core:
raise RuntimeError("HassIO already set!") raise RuntimeError("Hass.io already set!")
self._core = value self._core = value
@property @property

View File

@ -1,4 +1,4 @@
"""DBus interface objects.""" """D-Bus interface objects."""
from .systemd import Systemd from .systemd import Systemd
from .hostname import Hostname from .hostname import Hostname
@ -7,10 +7,10 @@ from ..coresys import CoreSysAttributes
class DBusManager(CoreSysAttributes): class DBusManager(CoreSysAttributes):
"""DBus Interface handler.""" """A DBus Interface handler."""
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize DBus Interface.""" """Initialize D-Bus interface."""
self.coresys = coresys self.coresys = coresys
self._systemd = Systemd() self._systemd = Systemd()
@ -19,21 +19,21 @@ class DBusManager(CoreSysAttributes):
@property @property
def systemd(self): def systemd(self):
"""Return Systemd Interface.""" """Return the systemd interface."""
return self._systemd return self._systemd
@property @property
def hostname(self): def hostname(self):
"""Return hostname Interface.""" """Return the hostname interface."""
return self._hostname return self._hostname
@property @property
def rauc(self): def rauc(self):
"""Return rauc Interface.""" """Return the rauc interface."""
return self._rauc return self._rauc
async def load(self): async def load(self):
"""Connect interfaces to dbus.""" """Connect interfaces to D-Bus."""
await self.systemd.connect() await self.systemd.connect()
await self.hostname.connect() await self.hostname.connect()
await self.rauc.connect() await self.rauc.connect()

View File

@ -1,4 +1,4 @@
"""DBus interface for hostname.""" """D-Bus interface for hostname."""
import logging import logging
from .interface import DBusInterface from .interface import DBusInterface
@ -13,10 +13,10 @@ DBUS_OBJECT = '/org/freedesktop/hostname1'
class Hostname(DBusInterface): class Hostname(DBusInterface):
"""Handle DBus interface for hostname/system.""" """Handle D-Bus interface for hostname/system."""
async def connect(self): async def connect(self):
"""Connect do bus.""" """Connect to system's D-Bus."""
try: try:
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError: except DBusError:

View File

@ -1,8 +1,8 @@
"""Interface class for dbus wrappers.""" """Interface class for D-Bus wrappers."""
class DBusInterface: class DBusInterface:
"""Handle DBus interface for hostname/system.""" """Handle D-Bus interface for hostname/system."""
def __init__(self): def __init__(self):
"""Initialize systemd.""" """Initialize systemd."""
@ -10,9 +10,9 @@ class DBusInterface:
@property @property
def is_connected(self): def is_connected(self):
"""Return True, if they is connected to dbus.""" """Return True, if they is connected to D-Bus."""
return self.dbus is not None return self.dbus is not None
async def connect(self): async def connect(self):
"""Connect do bus.""" """Connect to D-Bus."""
raise NotImplementedError() raise NotImplementedError()

View File

@ -1,4 +1,4 @@
"""DBus interface for rauc.""" """D-Bus interface for rauc."""
import logging import logging
from .interface import DBusInterface from .interface import DBusInterface
@ -13,10 +13,10 @@ DBUS_OBJECT = '/'
class Rauc(DBusInterface): class Rauc(DBusInterface):
"""Handle DBus interface for rauc.""" """Handle D-Bus interface for rauc."""
async def connect(self): async def connect(self):
"""Connect do bus.""" """Connect to D-Bus."""
try: try:
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError: except DBusError:

View File

@ -1,4 +1,4 @@
"""Interface to Systemd over dbus.""" """Interface to Systemd over D-Bus."""
import logging import logging
from .interface import DBusInterface from .interface import DBusInterface

View File

@ -1,12 +1,12 @@
"""Utils for dbus.""" """Utils for D-Bus."""
from ..exceptions import DBusNotConnectedError from ..exceptions import DBusNotConnectedError
def dbus_connected(method): def dbus_connected(method):
"""Wrapper for check if dbus is connected.""" """Wrapper for check if D-Bus is connected."""
def wrap_dbus(api, *args, **kwargs): def wrap_dbus(api, *args, **kwargs):
"""Check if dbus is connected before call a method.""" """Check if D-Bus is connected before call a method."""
if api.dbus is None: if api.dbus is None:
raise DBusNotConnectedError() raise DBusNotConnectedError()
return method(api, *args, **kwargs) return method(api, *args, **kwargs)

View File

@ -165,6 +165,13 @@ class DockerAddon(DockerInterface):
return 'host' return 'host'
return None return None
@property
def pid_mode(self):
"""Return PID mode for addon."""
if not self.addon.protected and self.addon.host_pid:
return 'host'
return None
@property @property
def volumes(self): def volumes(self):
"""Generate volumes for mappings.""" """Generate volumes for mappings."""
@ -277,6 +284,7 @@ class DockerAddon(DockerInterface):
ipc_mode=self.ipc, ipc_mode=self.ipc,
stdin_open=self.addon.with_stdin, stdin_open=self.addon.with_stdin,
network_mode=self.network_mode, network_mode=self.network_mode,
pid_mode=self.pid_mode,
ports=self.ports, ports=self.ports,
extra_hosts=self.network_mapping, extra_hosts=self.network_mapping,
devices=self.devices, devices=self.devices,

View File

@ -32,26 +32,32 @@ class DockerInterface(CoreSysAttributes):
"""Return name of docker container.""" """Return name of docker container."""
return None return None
@property
def meta_config(self):
"""Return meta data of config for container/image."""
if not self._meta:
return {}
return self._meta.get('Config', {})
@property
def meta_labels(self):
"""Return meta data of labels for container/image."""
return self.meta_config.get('Labels', {})
@property @property
def image(self): def image(self):
"""Return name of docker image.""" """Return name of docker image."""
if not self._meta: return self.meta_config.get('Image')
return None
return self._meta['Config']['Image']
@property @property
def version(self): def version(self):
"""Return version of docker image.""" """Return version of docker image."""
if self._meta and LABEL_VERSION in self._meta['Config']['Labels']: return self.meta_labels.get(LABEL_VERSION)
return self._meta['Config']['Labels'][LABEL_VERSION]
return None
@property @property
def arch(self): def arch(self):
"""Return arch of docker image.""" """Return arch of docker image."""
if self._meta and LABEL_ARCH in self._meta['Config']['Labels']: return self.meta_labels.get(LABEL_ARCH)
return self._meta['Config']['Labels'][LABEL_ARCH]
return None
@property @property
def in_progress(self): def in_progress(self):

View File

@ -68,7 +68,7 @@ class HassOS(CoreSysAttributes):
def _check_host(self): def _check_host(self):
"""Check if HassOS is availabe.""" """Check if HassOS is availabe."""
if not self.available: if not self.available:
_LOGGER.error("No HassOS availabe") _LOGGER.error("No HassOS available")
raise HassOSNotSupportedError() raise HassOSNotSupportedError()
async def _download_raucb(self, version): async def _download_raucb(self, version):
@ -97,7 +97,7 @@ class HassOS(CoreSysAttributes):
_LOGGER.warning("Can't fetch versions from %s: %s", url, err) _LOGGER.warning("Can't fetch versions from %s: %s", url, err)
except OSError as err: except OSError as err:
_LOGGER.error("Can't write ota file: %s", err) _LOGGER.error("Can't write OTA file: %s", err)
raise HassOSUpdateError() raise HassOSUpdateError()
@ -131,7 +131,7 @@ class HassOS(CoreSysAttributes):
""" """
self._check_host() self._check_host()
_LOGGER.info("Sync config from USB on HassOS.") _LOGGER.info("Syncing configuration from USB with HassOS.")
return self.sys_host.services.restart('hassos-config.service') return self.sys_host.services.restart('hassos-config.service')
async def update(self, version=None): async def update(self, version=None):
@ -182,5 +182,5 @@ class HassOS(CoreSysAttributes):
if await self.instance.update(version): if await self.instance.update(version):
return return
_LOGGER.error("HassOS CLI update fails.") _LOGGER.error("HassOS CLI update fails")
raise HassOSUpdateError() raise HassOSUpdateError()

View File

@ -36,10 +36,10 @@ ConfigResult = attr.make_class('ConfigResult', ['valid', 'log'], frozen=True)
class HomeAssistant(JsonConfig, CoreSysAttributes): class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Hass core object for handle it.""" """Home Assistant core object for handle it."""
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize hass object.""" """Initialize Home Assistant object."""
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)
@ -54,12 +54,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
if await self.instance.attach(): if await self.instance.attach():
return return
_LOGGER.info("No HomeAssistant docker %s found.", self.image) _LOGGER.info("No Home Assistant Docker image %s found.", self.image)
await self.install_landingpage() await self.install_landingpage()
@property @property
def machine(self): def machine(self):
"""Return System Machines.""" """Return the system machines."""
return self.instance.machine return self.instance.machine
@property @property
@ -74,76 +74,76 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def api_port(self): def api_port(self):
"""Return network port to home-assistant instance.""" """Return network port to Home Assistant instance."""
return self._data[ATTR_PORT] return self._data[ATTR_PORT]
@api_port.setter @api_port.setter
def api_port(self, value): def api_port(self, value):
"""Set network port for home-assistant instance.""" """Set network port for Home Assistant instance."""
self._data[ATTR_PORT] = value self._data[ATTR_PORT] = value
@property @property
def api_password(self): def api_password(self):
"""Return password for home-assistant instance.""" """Return password for Home Assistant instance."""
return self._data.get(ATTR_PASSWORD) return self._data.get(ATTR_PASSWORD)
@api_password.setter @api_password.setter
def api_password(self, value): def api_password(self, value):
"""Set password for home-assistant instance.""" """Set password for Home Assistant instance."""
self._data[ATTR_PASSWORD] = value self._data[ATTR_PASSWORD] = value
@property @property
def api_ssl(self): def api_ssl(self):
"""Return if we need ssl to home-assistant instance.""" """Return if we need ssl to Home Assistant instance."""
return self._data[ATTR_SSL] return self._data[ATTR_SSL]
@api_ssl.setter @api_ssl.setter
def api_ssl(self, value): def api_ssl(self, value):
"""Set SSL for home-assistant instance.""" """Set SSL for Home Assistant instance."""
self._data[ATTR_SSL] = value self._data[ATTR_SSL] = value
@property @property
def api_url(self): def api_url(self):
"""Return API url to Home-Assistant.""" """Return API url to Home Assistant."""
return "{}://{}:{}".format( return "{}://{}:{}".format(
'https' if self.api_ssl else 'http', self.api_ip, self.api_port 'https' if self.api_ssl else 'http', self.api_ip, self.api_port
) )
@property @property
def watchdog(self): def watchdog(self):
"""Return True if the watchdog should protect Home-Assistant.""" """Return True if the watchdog should protect Home Assistant."""
return self._data[ATTR_WATCHDOG] return self._data[ATTR_WATCHDOG]
@watchdog.setter @watchdog.setter
def watchdog(self, value): def watchdog(self, value):
"""Return True if the watchdog should protect Home-Assistant.""" """Return True if the watchdog should protect Home Assistant."""
self._data[ATTR_WATCHDOG] = value self._data[ATTR_WATCHDOG] = value
@property @property
def wait_boot(self): def wait_boot(self):
"""Return time to wait for Home-Assistant startup.""" """Return time to wait for Home Assistant startup."""
return self._data[ATTR_WAIT_BOOT] return self._data[ATTR_WAIT_BOOT]
@wait_boot.setter @wait_boot.setter
def wait_boot(self, value): def wait_boot(self, value):
"""Set time to wait for Home-Assistant startup.""" """Set time to wait for Home Assistant startup."""
self._data[ATTR_WAIT_BOOT] = value self._data[ATTR_WAIT_BOOT] = value
@property @property
def version(self): def version(self):
"""Return version of running homeassistant.""" """Return version of running Home Assistant."""
return self.instance.version return self.instance.version
@property @property
def last_version(self): def last_version(self):
"""Return last available version of homeassistant.""" """Return last available version of Home Assistant."""
if self.is_custom_image: if self.is_custom_image:
return self._data.get(ATTR_LAST_VERSION) return self._data.get(ATTR_LAST_VERSION)
return self.sys_updater.version_homeassistant return self.sys_updater.version_homeassistant
@last_version.setter @last_version.setter
def last_version(self, value): def last_version(self, value):
"""Set last available version of homeassistant.""" """Set last available version of Home Assistant."""
if value: if value:
self._data[ATTR_LAST_VERSION] = value self._data[ATTR_LAST_VERSION] = value
else: else:
@ -151,14 +151,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def image(self): def image(self):
"""Return image name of hass containter.""" """Return image name of the Home Assistant container."""
if self._data.get(ATTR_IMAGE): if self._data.get(ATTR_IMAGE):
return self._data[ATTR_IMAGE] return self._data[ATTR_IMAGE]
return os.environ['HOMEASSISTANT_REPOSITORY'] return os.environ['HOMEASSISTANT_REPOSITORY']
@image.setter @image.setter
def image(self, value): def image(self, value):
"""Set image name of hass containter.""" """Set image name of Home Assistant container."""
if value: if value:
self._data[ATTR_IMAGE] = value self._data[ATTR_IMAGE] = value
else: else:
@ -172,22 +172,22 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def boot(self): def boot(self):
"""Return True if home-assistant boot is enabled.""" """Return True if Home Assistant boot is enabled."""
return self._data[ATTR_BOOT] return self._data[ATTR_BOOT]
@boot.setter @boot.setter
def boot(self, value): def boot(self, value):
"""Set home-assistant boot options.""" """Set Home Assistant boot options."""
self._data[ATTR_BOOT] = value self._data[ATTR_BOOT] = value
@property @property
def uuid(self): def uuid(self):
"""Return a UUID of this HomeAssistant.""" """Return a UUID of this Home Assistant instance."""
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@property @property
def hassio_token(self): def hassio_token(self):
"""Return a access token for Hass.io API.""" """Return a access token for the Hass.io API."""
return self._data.get(ATTR_ACCESS_TOKEN) return self._data.get(ATTR_ACCESS_TOKEN)
@property @property
@ -266,7 +266,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
finally: finally:
if running: if running:
await self._start() await self._start()
_LOGGER.info("Successfull run HomeAssistant %s", to_version) _LOGGER.info("Successful run Home Assistant %s", to_version)
# Update Home Assistant # Update Home Assistant
with suppress(HomeAssistantError): with suppress(HomeAssistantError):
@ -281,9 +281,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
raise HomeAssistantUpdateError() raise HomeAssistantUpdateError()
async def _start(self): async def _start(self):
"""Start HomeAssistant docker & wait.""" """Start Home Assistant Docker & wait."""
if await self.instance.is_running(): if await self.instance.is_running():
_LOGGER.warning("HomeAssistant allready running!") _LOGGER.warning("Home Assistant is already running!")
return return
# Create new API token # Create new API token
@ -304,7 +304,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@process_lock @process_lock
def stop(self): def stop(self):
"""Stop HomeAssistant docker. """Stop Home Assistant Docker.
Return a coroutine. Return a coroutine.
""" """
@ -312,7 +312,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@process_lock @process_lock
async def restart(self): async def restart(self):
"""Restart HomeAssistant docker.""" """Restart Home Assistant Docker."""
await self.instance.stop() await self.instance.stop()
await self._start() await self._start()
@ -331,14 +331,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return self.instance.stats() return self.instance.stats()
def is_running(self): def is_running(self):
"""Return True if docker container is running. """Return True if Docker container is running.
Return a coroutine. Return a coroutine.
""" """
return self.instance.is_running() return self.instance.is_running()
def is_initialize(self): def is_initialize(self):
"""Return True if a docker container is exists. """Return True if a Docker container is exists.
Return a coroutine. Return a coroutine.
""" """
@ -350,7 +350,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return self.instance.in_progress or self.lock.locked() return self.instance.in_progress or self.lock.locked()
async def check_config(self): async def check_config(self):
"""Run homeassistant config check.""" """Run Home Assistant config check."""
result = await self.instance.execute_command( result = await self.instance.execute_command(
"python3 -m homeassistant -c /config --script check_config" "python3 -m homeassistant -c /config --script check_config"
) )
@ -429,14 +429,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
raise HomeAssistantAPIError() raise HomeAssistantAPIError()
async def check_api_state(self): async def check_api_state(self):
"""Return True if Home-Assistant up and running.""" """Return True if Home Assistant up and running."""
with suppress(HomeAssistantAPIError): with suppress(HomeAssistantAPIError):
async with self.make_request('get', 'api/') as resp: async with self.make_request('get', 'api/') as resp:
if resp.status in (200, 201): if resp.status in (200, 201):
return True return True
err = resp.status err = resp.status
_LOGGER.warning("Home-Assistant API config missmatch: %d", err) _LOGGER.warning("Home Assistant API config mismatch: %d", err)
return False return False
async def send_event(self, event_type, event_data=None): async def send_event(self, event_type, event_data=None):
@ -479,7 +479,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# 1 # 1
# Check if Container is is_running # Check if Container is is_running
if not await self.instance.is_running(): if not await self.instance.is_running():
_LOGGER.error("HomeAssistant is crashed!") _LOGGER.error("Home Assistant has crashed!")
break break
# 2 # 2
@ -504,7 +504,8 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# 4 # 4
# Timeout # Timeout
if time.monotonic() - start_time > self.wait_boot: if time.monotonic() - start_time > self.wait_boot:
_LOGGER.warning("Don't wait anymore of HomeAssistant startup!") _LOGGER.warning(
"Don't wait anymore of Home Assistant startup!")
break break
self._error_state = True self._error_state = True

View File

@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
class Supervisor(CoreSysAttributes): class Supervisor(CoreSysAttributes):
"""Hass core object for handle it.""" """Home Assistant core object for handle it."""
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize hass object.""" """Initialize hass object."""
@ -25,7 +25,7 @@ class Supervisor(CoreSysAttributes):
async def load(self): async def load(self):
"""Prepare Home Assistant object.""" """Prepare Home Assistant object."""
if not await self.instance.attach(): if not await self.instance.attach():
_LOGGER.fatal("Can't setup supervisor docker container!") _LOGGER.fatal("Can't setup Supervisor Docker container!")
await self.instance.cleanup() await self.instance.cleanup()
@property @property
@ -35,22 +35,22 @@ class Supervisor(CoreSysAttributes):
@property @property
def version(self): def version(self):
"""Return version of running homeassistant.""" """Return version of running Home Assistant."""
return self.instance.version return self.instance.version
@property @property
def last_version(self): def last_version(self):
"""Return last available version of homeassistant.""" """Return last available version of Home Assistant."""
return self.sys_updater.version_hassio return self.sys_updater.version_hassio
@property @property
def image(self): def image(self):
"""Return image name of hass containter.""" """Return image name of Home Assistant container."""
return self.instance.image return self.instance.image
@property @property
def arch(self): def arch(self):
"""Return arch of hass.io containter.""" """Return arch of the Hass.io container."""
return self.instance.arch return self.instance.arch
async def update_apparmor(self): async def update_apparmor(self):
@ -86,13 +86,13 @@ class Supervisor(CoreSysAttributes):
_LOGGER.warning("Version %s is already installed", version) _LOGGER.warning("Version %s is already installed", version)
return return
_LOGGER.info("Update supervisor to version %s", version) _LOGGER.info("Update Supervisor to version %s", version)
if await self.instance.install(version): if await self.instance.install(version):
await self.update_apparmor() await self.update_apparmor()
self.sys_loop.call_later(1, self.sys_loop.stop) self.sys_loop.call_later(1, self.sys_loop.stop)
return True return True
_LOGGER.error("Update of hass.io fails!") _LOGGER.error("Update of Hass.io fails!")
return False return False
@property @property

View File

@ -1,4 +1,4 @@
"""Multible tasks.""" """A collection of tasks."""
import asyncio import asyncio
import logging import logging
@ -22,7 +22,7 @@ RUN_WATCHDOG_HOMEASSISTANT_API = 300
class Tasks(CoreSysAttributes): class Tasks(CoreSysAttributes):
"""Handle Tasks inside HassIO.""" """Handle Tasks inside Hass.io."""
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize Tasks.""" """Initialize Tasks."""
@ -58,7 +58,7 @@ class Tasks(CoreSysAttributes):
_LOGGER.info("All core tasks are scheduled") _LOGGER.info("All core tasks are scheduled")
async def _update_addons(self): async def _update_addons(self):
"""Check if an update is available for an addon and update it.""" """Check if an update is available for an Add-on and update it."""
tasks = [] tasks = []
for addon in self.sys_addons.list_addons: for addon in self.sys_addons.list_addons:
if not addon.is_installed or not addon.auto_update: if not addon.is_installed or not addon.auto_update:
@ -67,18 +67,18 @@ class Tasks(CoreSysAttributes):
if addon.version_installed == addon.last_version: if addon.version_installed == addon.last_version:
continue continue
if addon.test_udpate_schema(): if addon.test_update_schema():
tasks.append(addon.update()) tasks.append(addon.update())
else: else:
_LOGGER.warning( _LOGGER.warning(
"Addon %s will be ignore, schema tests fails", addon.slug) "Add-on %s will be ignore, schema tests fails", addon.slug)
if tasks: if tasks:
_LOGGER.info("Addon auto update process %d tasks", len(tasks)) _LOGGER.info("Add-on auto update process %d tasks", len(tasks))
await asyncio.wait(tasks) await asyncio.wait(tasks)
async def _update_supervisor(self): async def _update_supervisor(self):
"""Check and run update of supervisor hassio.""" """Check and run update of Supervisor Hass.io."""
if not self.sys_supervisor.need_update: if not self.sys_supervisor.need_update:
return return
@ -91,23 +91,23 @@ class Tasks(CoreSysAttributes):
await self.sys_supervisor.update() await self.sys_supervisor.update()
async def _watchdog_homeassistant_docker(self): async def _watchdog_homeassistant_docker(self):
"""Check running state of docker and start if they is close.""" """Check running state of Docker and start if they is close."""
# if Home-Assistant is active # if Home Assistant is active
if not await self.sys_homeassistant.is_initialize() or \ if not await self.sys_homeassistant.is_initialize() or \
not self.sys_homeassistant.watchdog or \ not self.sys_homeassistant.watchdog or \
self.sys_homeassistant.error_state: self.sys_homeassistant.error_state:
return return
# if Home-Assistant is running # if Home Assistant is running
if self.sys_homeassistant.in_progress or \ if self.sys_homeassistant.in_progress or \
await self.sys_homeassistant.is_running(): await self.sys_homeassistant.is_running():
return return
_LOGGER.warning("Watchdog found a problem with Home-Assistant docker!") _LOGGER.warning("Watchdog found a problem with Home Assistant Docker!")
await self.sys_homeassistant.start() await self.sys_homeassistant.start()
async def _watchdog_homeassistant_api(self): async def _watchdog_homeassistant_api(self):
"""Create scheduler task for montoring running state of API. """Create scheduler task for monitoring running state of API.
Try 2 times to call API before we restart Home-Assistant. Maybe we had Try 2 times to call API before we restart Home-Assistant. Maybe we had
a delay in our system. a delay in our system.
@ -130,10 +130,10 @@ class Tasks(CoreSysAttributes):
retry_scan += 1 retry_scan += 1
if retry_scan == 1: if retry_scan == 1:
self._cache[HASS_WATCHDOG_API] = retry_scan self._cache[HASS_WATCHDOG_API] = retry_scan
_LOGGER.warning("Watchdog miss API response from Home-Assistant") _LOGGER.warning("Watchdog miss API response from Home Assistant")
return return
_LOGGER.error("Watchdog found a problem with Home-Assistant API!") _LOGGER.error("Watchdog found a problem with Home Assistant API!")
try: try:
await self.sys_homeassistant.restart() await self.sys_homeassistant.restart()
finally: finally:

View File

@ -39,27 +39,27 @@ class Updater(JsonConfig, CoreSysAttributes):
@property @property
def version_homeassistant(self): def version_homeassistant(self):
"""Return last version of homeassistant.""" """Return last version of Home Assistant."""
return self._data.get(ATTR_HOMEASSISTANT) return self._data.get(ATTR_HOMEASSISTANT)
@property @property
def version_hassio(self): def version_hassio(self):
"""Return last version of hassio.""" """Return last version of Hass.io."""
return self._data.get(ATTR_HASSIO) return self._data.get(ATTR_HASSIO)
@property @property
def version_hassos(self): def version_hassos(self):
"""Return last version of hassos.""" """Return last version of HassOS."""
return self._data.get(ATTR_HASSOS) return self._data.get(ATTR_HASSOS)
@property @property
def version_hassos_cli(self): def version_hassos_cli(self):
"""Return last version of hassos cli.""" """Return last version of HassOS cli."""
return self._data.get(ATTR_HASSOS_CLI) return self._data.get(ATTR_HASSOS_CLI)
@property @property
def channel(self): def channel(self):
"""Return upstream channel of hassio instance.""" """Return upstream channel of Hass.io instance."""
return self._data[ATTR_CHANNEL] return self._data[ATTR_CHANNEL]
@channel.setter @channel.setter
@ -69,7 +69,7 @@ class Updater(JsonConfig, CoreSysAttributes):
@AsyncThrottle(timedelta(seconds=60)) @AsyncThrottle(timedelta(seconds=60))
async def fetch_data(self): async def fetch_data(self):
"""Fetch current versions from github. """Fetch current versions from Github.
Is a coroutine. Is a coroutine.
""" """

View File

@ -1,4 +1,4 @@
"""Tools file for HassIO.""" """Tools file for Hass.io."""
from datetime import datetime from datetime import datetime
import hashlib import hashlib
import logging import logging
@ -25,7 +25,8 @@ def process_lock(method):
"""Return api wrapper.""" """Return api wrapper."""
if api.lock.locked(): if api.lock.locked():
_LOGGER.error( _LOGGER.error(
"Can't excute %s while a task is in progress", method.__name__) "Can't execute %s while a task is in progress",
method.__name__)
return False return False
async with api.lock: async with api.lock:

View File

@ -1,4 +1,4 @@
"""Some functions around apparmor profiles.""" """Some functions around AppArmor profiles."""
import logging import logging
import re import re
@ -21,7 +21,7 @@ def get_profile_name(profile_file):
continue continue
profiles.add(match.group(1)) profiles.add(match.group(1))
except OSError as err: except OSError as err:
_LOGGER.error("Can't read apparmor profile: %s", err) _LOGGER.error("Can't read AppArmor profile: %s", err)
raise AppArmorFileError() raise AppArmorFileError()
if len(profiles) != 1: if len(profiles) != 1:

View File

@ -1,4 +1,4 @@
"""Tools file for HassIO.""" """Tools file for Hass.io."""
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import logging import logging
import re import re
@ -58,5 +58,5 @@ def parse_datetime(dt_str):
def utcnow(): def utcnow():
"""Returns current timestamp including timezone.""" """Return the current timestamp including timezone."""
return datetime.now(UTC) return datetime.now(UTC)

View File

@ -1,4 +1,4 @@
"""Tools file for HassIO.""" """Tools file for Hass.io."""
import json import json
import logging import logging
@ -9,14 +9,14 @@ _LOGGER = logging.getLogger(__name__)
def write_json_file(jsonfile, data): def write_json_file(jsonfile, data):
"""Write a json file.""" """Write a JSON file."""
json_str = json.dumps(data, indent=2) json_str = json.dumps(data, indent=2)
with jsonfile.open('w') as conf_file: with jsonfile.open('w') as conf_file:
conf_file.write(json_str) conf_file.write(json_str)
def read_json_file(jsonfile): def read_json_file(jsonfile):
"""Read a json file and return a dict.""" """Read a JSON file and return a dict."""
with jsonfile.open('r') as cfile: with jsonfile.open('r') as cfile:
return json.loads(cfile.read()) return json.loads(cfile.read())
@ -33,7 +33,7 @@ class JsonConfig:
self.read_data() self.read_data()
def reset_data(self): def reset_data(self):
"""Reset json file to default.""" """Reset JSON file to default."""
try: try:
self._data = self._schema({}) self._data = self._schema({})
except vol.Invalid as ex: except vol.Invalid as ex:
@ -41,7 +41,7 @@ class JsonConfig:
self._file, humanize_error(self._data, ex)) self._file, humanize_error(self._data, ex))
def read_data(self): def read_data(self):
"""Read json file & validate.""" """Read JSON file & validate."""
if self._file.is_file(): if self._file.is_file():
try: try:
self._data = read_json_file(self._file) self._data = read_json_file(self._file)
@ -61,7 +61,7 @@ class JsonConfig:
self._data = self._schema({}) self._data = self._schema({})
def save_data(self): def save_data(self):
"""Store data to config file.""" """Store data to configuration file."""
# Validate # Validate
try: try:
self._data = self._schema(self._data) self._data = self._schema(self._data)
@ -78,4 +78,5 @@ class JsonConfig:
try: try:
write_json_file(self._file, self._data) write_json_file(self._file, self._data)
except (OSError, json.JSONDecodeError) as err: except (OSError, json.JSONDecodeError) as err:
_LOGGER.error("Can't store config in %s: %s", self._file, err) _LOGGER.error(
"Can't store configuration in %s: %s", self._file, err)

View File

@ -24,7 +24,7 @@ CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
def validate_repository(repository): def validate_repository(repository):
"""Validate a valide repository.""" """Validate a valid repository."""
data = RE_REPOSITORY.match(repository) data = RE_REPOSITORY.match(repository)
if not data: if not data:
raise vol.Invalid("No valid repository format!") raise vol.Invalid("No valid repository format!")
@ -55,7 +55,7 @@ def validate_timezone(timezone):
# pylint: disable=inconsistent-return-statements # pylint: disable=inconsistent-return-statements
def convert_to_docker_ports(data): def convert_to_docker_ports(data):
"""Convert data into docker port list.""" """Convert data into Docker port list."""
# dynamic ports # dynamic ports
if data is None: if data is None:
return None return None
@ -72,7 +72,7 @@ def convert_to_docker_ports(data):
if isinstance(data, list) and len(data) == 2: if isinstance(data, list) and len(data) == 2:
return (vol.Coerce(str)(data[0]), NETWORK_PORT(data[1])) return (vol.Coerce(str)(data[0]), NETWORK_PORT(data[1]))
raise vol.Invalid("Can't validate docker host settings") raise vol.Invalid("Can't validate Docker host settings")
DOCKER_PORTS = vol.Schema({ DOCKER_PORTS = vol.Schema({