From 5f3dd6190aa1e75d94db980788d80564d0bf6bf3 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 10 Sep 2018 00:02:27 +0200 Subject: [PATCH 1/8] Bump version 130 --- hassio/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/const.py b/hassio/const.py index 93db56f8b..5bea28a92 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -2,7 +2,7 @@ from pathlib import Path from ipaddress import ip_network -HASSIO_VERSION = '130' +HASSIO_VERSION = '131' URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" URL_HASSIO_VERSION = \ From 3d459f1b8bb1528ee440864361c4bab11cea304d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 15 Sep 2018 22:05:50 +0200 Subject: [PATCH 2/8] :sparkles: Adds support for SYS_PTRACE add-on privileges (#697) --- hassio/addons/utils.py | 4 ++-- hassio/addons/validate.py | 3 ++- hassio/const.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hassio/addons/utils.py b/hassio/addons/utils.py index 83f05ad2e..b9f1a9437 100644 --- a/hassio/addons/utils.py +++ b/hassio/addons/utils.py @@ -6,7 +6,7 @@ import re from ..const import ( SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN, - PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO) + PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE) RE_SHA1 = re.compile(r"[a-f0-9]{8}") @@ -33,7 +33,7 @@ def rating_security(addon): # Privileged options if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, - PRIVILEGED_SYS_RAWIO): + PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE): rating += -1 # Not secure Networking diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 275465a60..4ff3d8cfa 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -22,7 +22,7 @@ from ..const import ( ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE, - PRIVILEGED_SYS_RESOURCE) + PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE) from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE _LOGGER = logging.getLogger(__name__) @@ -69,6 +69,7 @@ PRIVILEGED_ALL = [ PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE, PRIVILEGED_SYS_RESOURCE, + PRIVILEGED_SYS_PTRACE, ] BASE_IMAGE = { diff --git a/hassio/const.py b/hassio/const.py index 5bea28a92..da4e15542 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -238,6 +238,7 @@ PRIVILEGED_IPC_LOCK = 'IPC_LOCK' PRIVILEGED_SYS_TIME = 'SYS_TIME' PRIVILEGED_SYS_NICE = 'SYS_NICE' PRIVILEGED_SYS_RESOURCE = 'SYS_RESOURCE' +PRIVILEGED_SYS_PTRACE = 'SYS_PTRACE' FEATURES_SHUTDOWN = 'shutdown' FEATURES_REBOOT = 'reboot' From 061420f279157cab78f3a6d483a44b05c363e6f2 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sat, 15 Sep 2018 22:07:05 +0200 Subject: [PATCH 3/8] Make Label handling more robust (#696) * Make Label handling more robust * Update interface.py * Update interface.py * Update interface.py --- hassio/docker/interface.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/hassio/docker/interface.py b/hassio/docker/interface.py index 3c2a8c879..ae3f2e0be 100644 --- a/hassio/docker/interface.py +++ b/hassio/docker/interface.py @@ -32,26 +32,32 @@ class DockerInterface(CoreSysAttributes): """Return name of docker container.""" 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 def image(self): """Return name of docker image.""" - if not self._meta: - return None - return self._meta['Config']['Image'] + return self.meta_config.get('Image') @property def version(self): """Return version of docker image.""" - if self._meta and LABEL_VERSION in self._meta['Config']['Labels']: - return self._meta['Config']['Labels'][LABEL_VERSION] - return None + return self.meta_labels.get(LABEL_VERSION) @property def arch(self): """Return arch of docker image.""" - if self._meta and LABEL_ARCH in self._meta['Config']['Labels']: - return self._meta['Config']['Labels'][LABEL_ARCH] - return None + return self.meta_labels.get(LABEL_ARCH) @property def in_progress(self): From 622e99e04cd6f886a943ea43dfe7722f909cd164 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 17 Sep 2018 21:02:28 +0200 Subject: [PATCH 4/8] Adds host PID mode support for add-ons (#700) * :sparkles: Adds host PID mode support for add-ons. * :lock: Disables host PID mode when in protected mode * :vertical_traffic_light: Adds more negative rating weight to host PID mode --- API.md | 1 + hassio/addons/addon.py | 7 ++++++- hassio/addons/utils.py | 4 ++++ hassio/addons/validate.py | 3 ++- hassio/api/addons.py | 3 ++- hassio/const.py | 1 + hassio/docker/addon.py | 8 ++++++++ 7 files changed, 24 insertions(+), 3 deletions(-) diff --git a/API.md b/API.md index 1b69a9c8f..d8deb5cfc 100644 --- a/API.md +++ b/API.md @@ -472,6 +472,7 @@ Get all available addons. "options": "{}", "network": "{}|null", "host_network": "bool", + "host_pid": "bool", "host_ipc": "bool", "host_dbus": "bool", "privileged": ["NET_ADMIN", "SYS_ADMIN"], diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 7e207ff94..62962db12 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -26,7 +26,7 @@ from ..const import ( ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS, - ATTR_PROTECTED, ATTR_ACCESS_TOKEN, + ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT) from ..coresys import CoreSysAttributes from ..docker.addon import DockerAddon @@ -307,6 +307,11 @@ class Addon(CoreSysAttributes): """Return True if addon run on 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 def host_ipc(self): """Return True if addon run on host IPC namespace.""" diff --git a/hassio/addons/utils.py b/hassio/addons/utils.py index b9f1a9437..7c2e4ba31 100644 --- a/hassio/addons/utils.py +++ b/hassio/addons/utils.py @@ -40,6 +40,10 @@ def rating_security(addon): if addon.host_network: rating += -1 + # Insecure PID namespace + if addon.host_pid: + rating += -2 + # Full Access if addon.with_full_access: rating += -2 diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 4ff3d8cfa..43ee5c3cf 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -19,7 +19,7 @@ from ..const import ( ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED, - ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, + ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE, PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE) @@ -105,6 +105,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ vol.Optional(ATTR_WEBUI): vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"), 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_DBUS, default=False): vol.Boolean(), vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")], diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 0e630c0bb..af3a3b515 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -19,7 +19,7 @@ from ..const import ( 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_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, REQUEST_FROM) from ..coresys import CoreSysAttributes @@ -140,6 +140,7 @@ class APIAddons(CoreSysAttributes): ATTR_BUILD: addon.need_build, ATTR_NETWORK: addon.ports, ATTR_HOST_NETWORK: addon.host_network, + ATTR_HOST_PID: addon.host_pid, ATTR_HOST_IPC: addon.host_ipc, ATTR_HOST_DBUS: addon.host_dbus, ATTR_PRIVILEGED: addon.privileged, diff --git a/hassio/const.py b/hassio/const.py index da4e15542..664798d27 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -114,6 +114,7 @@ ATTR_BUILD = 'build' ATTR_DEVICES = 'devices' ATTR_ENVIRONMENT = 'environment' ATTR_HOST_NETWORK = 'host_network' +ATTR_HOST_PID = 'host_pid' ATTR_HOST_IPC = 'host_ipc' ATTR_HOST_DBUS = 'host_dbus' ATTR_NETWORK = 'network' diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index 232721b72..53aa9e96a 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -165,6 +165,13 @@ class DockerAddon(DockerInterface): return 'host' 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 def volumes(self): """Generate volumes for mappings.""" @@ -277,6 +284,7 @@ class DockerAddon(DockerInterface): ipc_mode=self.ipc, stdin_open=self.addon.with_stdin, network_mode=self.network_mode, + pid_mode=self.pid_mode, ports=self.ports, extra_hosts=self.network_mapping, devices=self.devices, From 17904d70d88c0cf01759e5c653757ea787c871e7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 17 Sep 2018 21:03:14 +0200 Subject: [PATCH 5/8] :rocket: Adds venv to .dockerignore (#701) --- .dockerignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockerignore b/.dockerignore index 7a948d6f8..c47f5f91e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,7 @@ # Temporary files **/__pycache__ + +# virtualenv +venv/ +ENV/ From f5845564db6cb0ba9ff5140a2c315cfbb7771f4f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 17 Sep 2018 23:11:53 +0200 Subject: [PATCH 6/8] :shirt: Fixes a typo in method name (#702) --- hassio/addons/addon.py | 2 +- hassio/tasks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 62962db12..68a3dfcfb 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -608,7 +608,7 @@ class Addon(CoreSysAttributes): return vol.Schema(dict) 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.""" if not self.is_installed or self.is_detached: return True diff --git a/hassio/tasks.py b/hassio/tasks.py index 5a0abdd8e..f9ae7343f 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -67,7 +67,7 @@ class Tasks(CoreSysAttributes): if addon.version_installed == addon.last_version: continue - if addon.test_udpate_schema(): + if addon.test_update_schema(): tasks.append(addon.update()) else: _LOGGER.warning( From c2299ef8da4e757bae81a8ff848a2d4bb28bec75 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 18 Sep 2018 18:17:20 +0200 Subject: [PATCH 7/8] Fix typos (#704) --- hassio/__init__.py | 2 +- hassio/__main__.py | 10 ++-- hassio/bootstrap.py | 34 ++++++------ hassio/config.py | 38 ++++++------- hassio/const.py | 2 +- hassio/core.py | 8 +-- hassio/coresys.py | 15 +++-- hassio/dbus/__init__.py | 14 ++--- hassio/dbus/hostname.py | 6 +- hassio/dbus/interface.py | 8 +-- hassio/dbus/rauc.py | 6 +- hassio/dbus/systemd.py | 2 +- hassio/dbus/utils.py | 6 +- hassio/hassos.py | 8 +-- hassio/homeassistant.py | 117 ++++++++++++++++++++------------------- hassio/supervisor.py | 22 ++++---- hassio/tasks.py | 26 ++++----- hassio/updater.py | 12 ++-- hassio/utils/__init__.py | 5 +- hassio/utils/apparmor.py | 4 +- hassio/utils/dt.py | 4 +- hassio/utils/json.py | 15 ++--- hassio/validate.py | 6 +- 23 files changed, 186 insertions(+), 184 deletions(-) diff --git a/hassio/__init__.py b/hassio/__init__.py index 994410970..e334e96c0 100644 --- a/hassio/__init__.py +++ b/hassio/__init__.py @@ -1 +1 @@ -"""Init file for HassIO.""" +"""Init file for Hass.io.""" diff --git a/hassio/__main__.py b/hassio/__main__.py index 129b46e29..4a0f6f44f 100644 --- a/hassio/__main__.py +++ b/hassio/__main__.py @@ -1,4 +1,4 @@ -"""Main file for HassIO.""" +"""Main file for Hass.io.""" import asyncio from concurrent.futures import ThreadPoolExecutor import logging @@ -31,7 +31,7 @@ if __name__ == "__main__": executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") loop.set_default_executor(executor) - _LOGGER.info("Initialize Hassio setup") + _LOGGER.info("Initialize Hass.io setup") coresys = bootstrap.initialize_coresys(loop) bootstrap.migrate_system_env(coresys) @@ -43,13 +43,13 @@ if __name__ == "__main__": loop.call_soon_threadsafe(bootstrap.reg_signal, loop) try: - _LOGGER.info("Run HassIO") + _LOGGER.info("Run Hass.io") loop.run_forever() finally: - _LOGGER.info("Stopping HassIO") + _LOGGER.info("Stopping Hass.io") loop.run_until_complete(coresys.core.stop()) executor.shutdown(wait=False) loop.close() - _LOGGER.info("Close Hassio") + _LOGGER.info("Close Hass.io") sys.exit(0) diff --git a/hassio/bootstrap.py b/hassio/bootstrap.py index 9773d04d8..012205410 100644 --- a/hassio/bootstrap.py +++ b/hassio/bootstrap.py @@ -1,4 +1,4 @@ -"""Bootstrap HassIO.""" +"""Bootstrap Hass.io.""" import logging import os import signal @@ -62,55 +62,55 @@ def initialize_coresys(loop): def initialize_system_data(coresys): - """Setup default config and create folders.""" + """Set up the default configuration and create folders.""" config = coresys.config - # homeassistant config folder + # Home Assistant configuration folder if not config.path_homeassistant.is_dir(): _LOGGER.info( - "Create Home-Assistant config folder %s", + "Create Home Assistant configuration folder %s", config.path_homeassistant) config.path_homeassistant.mkdir() # hassio ssl folder 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() # hassio addon data folder if not config.path_addons_data.is_dir(): _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) 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.mkdir(parents=True) 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.mkdir(parents=True) # hassio tmp folder 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) # hassio backup folder 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() # share folder 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() # apparmor folder 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() return config @@ -126,7 +126,7 @@ def migrate_system_env(coresys): try: old_build.rmdir() except OSError: - _LOGGER.warning("Can't cleanup old addons build dir.") + _LOGGER.warning("Can't cleanup old Add-on build directory") def initialize_logging(): @@ -166,24 +166,24 @@ def check_environment(): # check docker socket if not SOCKET_DOCKER.is_socket(): - _LOGGER.fatal("Can't find docker socket!") + _LOGGER.fatal("Can't find Docker socket!") return False # check socat exec if not shutil.which('socat'): - _LOGGER.fatal("Can't find socat program!") + _LOGGER.fatal("Can't find socat!") return False # check socat exec if not shutil.which('gdbus'): - _LOGGER.fatal("Can't find gdbus program!") + _LOGGER.fatal("Can't find gdbus!") return False return True def reg_signal(loop): - """Register SIGTERM, SIGKILL to stop system.""" + """Register SIGTERM and SIGKILL to stop system.""" try: loop.add_signal_handler( signal.SIGTERM, lambda: loop.call_soon(loop.stop)) diff --git a/hassio/config.py b/hassio/config.py index c4198352d..6232e9eb8 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -1,4 +1,4 @@ -"""Bootstrap HassIO.""" +"""Bootstrap Hass.io.""" from datetime import datetime import logging import os @@ -16,7 +16,7 @@ from .validate import SCHEMA_HASSIO_CONFIG _LOGGER = logging.getLogger(__name__) -HOMEASSISTANT_CONFIG = PurePath("homeassistant") +HOMEASSISTANT_CONFIG = PurePath('homeassistant') HASSIO_SSL = PurePath("ssl") @@ -56,7 +56,7 @@ class CoreConfig(JsonConfig): timezone = data.group('timezone') pytz.timezone(timezone) except (pytz.exceptions.UnknownTimeZoneError, OSError, AssertionError): - _LOGGER.debug("Can't parse HomeAssistant timezone") + _LOGGER.debug("Can't parse Home Assistant timezone") return self._data[ATTR_TIMEZONE] return timezone @@ -93,17 +93,17 @@ class CoreConfig(JsonConfig): @property def path_hassio(self): - """Return hassio data path.""" + """Return Hass.io data path.""" return HASSIO_DATA @property 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']) @property 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)) @property @@ -113,7 +113,7 @@ class CoreConfig(JsonConfig): @property 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)) @property @@ -123,42 +123,42 @@ class CoreConfig(JsonConfig): @property def path_addons_core(self): - """Return git path for core addons.""" + """Return git path for core Add-ons.""" return Path(HASSIO_DATA, ADDONS_CORE) @property def path_addons_git(self): - """Return path for git addons.""" + """Return path for Git Add-on.""" return Path(HASSIO_DATA, ADDONS_GIT) @property def path_addons_local(self): - """Return path for customs addons.""" + """Return path for custom Add-ons.""" return Path(HASSIO_DATA, ADDONS_LOCAL) @property 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) @property def path_addons_data(self): - """Return root addon data folder.""" + """Return root Add-on data folder.""" return Path(HASSIO_DATA, ADDONS_DATA) @property 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) @property def path_tmp(self): - """Return hass.io temp folder.""" + """Return Hass.io temp folder.""" return Path(HASSIO_DATA, TMP_DATA) @property 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) @property @@ -168,7 +168,7 @@ class CoreConfig(JsonConfig): @property 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) @property @@ -178,17 +178,17 @@ class CoreConfig(JsonConfig): @property def path_apparmor(self): - """Return root apparmor profile folder.""" + """Return root Apparmor profile folder.""" return Path(HASSIO_DATA, APPARMOR_DATA) @property 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) @property def addons_repositories(self): - """Return list of addons custom repositories.""" + """Return list of custom Add-on repositories.""" return self._data[ATTR_ADDONS_CUSTOM_LIST] def add_addon_repository(self, repo): diff --git a/hassio/const.py b/hassio/const.py index 664798d27..7965b4694 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,4 +1,4 @@ -"""Const file for HassIO.""" +"""Constants file for Hass.io.""" from pathlib import Path from ipaddress import ip_network diff --git a/hassio/core.py b/hassio/core.py index 2687a299d..be32ee89f 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -1,4 +1,4 @@ -"""Main file for HassIO.""" +"""Main file for Hass.io.""" from contextlib import suppress import asyncio import logging @@ -14,10 +14,10 @@ _LOGGER = logging.getLogger(__name__) class HassIO(CoreSysAttributes): - """Main object of hassio.""" + """Main object of Hass.io.""" def __init__(self, coresys): - """Initialize hassio object.""" + """Initialize Hass.io object.""" self.coresys = coresys async def setup(self): @@ -56,7 +56,7 @@ class HassIO(CoreSysAttributes): self.sys_create_task(self.sys_dns.start()) async def start(self): - """Start HassIO orchestration.""" + """Start Hass.io orchestration.""" # on release channel, try update itself # on dev mode, only read new versions if not self.sys_dev and self.sys_supervisor.need_update: diff --git a/hassio/coresys.py b/hassio/coresys.py index 5e27edd36..b0bc7739a 100644 --- a/hassio/coresys.py +++ b/hassio/coresys.py @@ -1,5 +1,4 @@ """Handle core shared data.""" - import aiohttp from .const import CHANNEL_DEV @@ -49,21 +48,21 @@ class CoreSys: @property def arch(self): - """Return running arch of hass.io system.""" + """Return running arch of the Hass.io system.""" if self._supervisor: return self._supervisor.arch return None @property def machine(self): - """Return running machine type of hass.io system.""" + """Return running machine type of the Hass.io system.""" if self._homeassistant: return self._homeassistant.machine return None @property def dev(self): - """Return True if we run dev modus.""" + """Return True if we run dev mode.""" return self._updater.channel == CHANNEL_DEV @property @@ -118,21 +117,21 @@ class CoreSys: @core.setter def core(self, value): - """Set a HassIO object.""" + """Set a Hass.io object.""" if self._core: - raise RuntimeError("HassIO already set!") + raise RuntimeError("Hass.io already set!") self._core = value @property def homeassistant(self): - """Return HomeAssistant object.""" + """Return Home Assistant object.""" return self._homeassistant @homeassistant.setter def homeassistant(self, value): """Set a HomeAssistant object.""" if self._homeassistant: - raise RuntimeError("HomeAssistant already set!") + raise RuntimeError("Home Assistant already set!") self._homeassistant = value @property diff --git a/hassio/dbus/__init__.py b/hassio/dbus/__init__.py index be8f8a3db..20941d566 100644 --- a/hassio/dbus/__init__.py +++ b/hassio/dbus/__init__.py @@ -1,4 +1,4 @@ -"""DBus interface objects.""" +"""D-Bus interface objects.""" from .systemd import Systemd from .hostname import Hostname @@ -7,10 +7,10 @@ from ..coresys import CoreSysAttributes class DBusManager(CoreSysAttributes): - """DBus Interface handler.""" + """A DBus Interface handler.""" def __init__(self, coresys): - """Initialize DBus Interface.""" + """Initialize D-Bus interface.""" self.coresys = coresys self._systemd = Systemd() @@ -19,21 +19,21 @@ class DBusManager(CoreSysAttributes): @property def systemd(self): - """Return Systemd Interface.""" + """Return the systemd interface.""" return self._systemd @property def hostname(self): - """Return hostname Interface.""" + """Return the hostname interface.""" return self._hostname @property def rauc(self): - """Return rauc Interface.""" + """Return the rauc interface.""" return self._rauc async def load(self): - """Connect interfaces to dbus.""" + """Connect interfaces to D-Bus.""" await self.systemd.connect() await self.hostname.connect() await self.rauc.connect() diff --git a/hassio/dbus/hostname.py b/hassio/dbus/hostname.py index e9c43b838..8a3530d46 100644 --- a/hassio/dbus/hostname.py +++ b/hassio/dbus/hostname.py @@ -1,4 +1,4 @@ -"""DBus interface for hostname.""" +"""D-Bus interface for hostname.""" import logging from .interface import DBusInterface @@ -13,10 +13,10 @@ DBUS_OBJECT = '/org/freedesktop/hostname1' class Hostname(DBusInterface): - """Handle DBus interface for hostname/system.""" + """Handle D-Bus interface for hostname/system.""" async def connect(self): - """Connect do bus.""" + """Connect to system's D-Bus.""" try: self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) except DBusError: diff --git a/hassio/dbus/interface.py b/hassio/dbus/interface.py index fcdf8de25..751cb3563 100644 --- a/hassio/dbus/interface.py +++ b/hassio/dbus/interface.py @@ -1,8 +1,8 @@ -"""Interface class for dbus wrappers.""" +"""Interface class for D-Bus wrappers.""" class DBusInterface: - """Handle DBus interface for hostname/system.""" + """Handle D-Bus interface for hostname/system.""" def __init__(self): """Initialize systemd.""" @@ -10,9 +10,9 @@ class DBusInterface: @property 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 async def connect(self): - """Connect do bus.""" + """Connect to D-Bus.""" raise NotImplementedError() diff --git a/hassio/dbus/rauc.py b/hassio/dbus/rauc.py index c656b38f4..d3dc15502 100644 --- a/hassio/dbus/rauc.py +++ b/hassio/dbus/rauc.py @@ -1,4 +1,4 @@ -"""DBus interface for rauc.""" +"""D-Bus interface for rauc.""" import logging from .interface import DBusInterface @@ -13,10 +13,10 @@ DBUS_OBJECT = '/' class Rauc(DBusInterface): - """Handle DBus interface for rauc.""" + """Handle D-Bus interface for rauc.""" async def connect(self): - """Connect do bus.""" + """Connect to D-Bus.""" try: self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) except DBusError: diff --git a/hassio/dbus/systemd.py b/hassio/dbus/systemd.py index 87740caa1..c1efe178a 100644 --- a/hassio/dbus/systemd.py +++ b/hassio/dbus/systemd.py @@ -1,4 +1,4 @@ -"""Interface to Systemd over dbus.""" +"""Interface to Systemd over D-Bus.""" import logging from .interface import DBusInterface diff --git a/hassio/dbus/utils.py b/hassio/dbus/utils.py index 94da81d9a..5180f385a 100644 --- a/hassio/dbus/utils.py +++ b/hassio/dbus/utils.py @@ -1,12 +1,12 @@ -"""Utils for dbus.""" +"""Utils for D-Bus.""" from ..exceptions import DBusNotConnectedError 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): - """Check if dbus is connected before call a method.""" + """Check if D-Bus is connected before call a method.""" if api.dbus is None: raise DBusNotConnectedError() return method(api, *args, **kwargs) diff --git a/hassio/hassos.py b/hassio/hassos.py index 8eefba710..05c36dcb4 100644 --- a/hassio/hassos.py +++ b/hassio/hassos.py @@ -68,7 +68,7 @@ class HassOS(CoreSysAttributes): def _check_host(self): """Check if HassOS is availabe.""" if not self.available: - _LOGGER.error("No HassOS availabe") + _LOGGER.error("No HassOS available") raise HassOSNotSupportedError() async def _download_raucb(self, version): @@ -97,7 +97,7 @@ class HassOS(CoreSysAttributes): _LOGGER.warning("Can't fetch versions from %s: %s", url, 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() @@ -131,7 +131,7 @@ class HassOS(CoreSysAttributes): """ 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') async def update(self, version=None): @@ -182,5 +182,5 @@ class HassOS(CoreSysAttributes): if await self.instance.update(version): return - _LOGGER.error("HassOS CLI update fails.") + _LOGGER.error("HassOS CLI update fails") raise HassOSUpdateError() diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 819043475..b2370f4ed 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -1,4 +1,4 @@ -"""HomeAssistant control object.""" +"""Home Assistant control object.""" import asyncio from contextlib import asynccontextmanager, suppress from datetime import datetime, timedelta @@ -36,10 +36,10 @@ ConfigResult = attr.make_class('ConfigResult', ['valid', 'log'], frozen=True) class HomeAssistant(JsonConfig, CoreSysAttributes): - """Hass core object for handle it.""" + """Home Assistant core object for handle it.""" def __init__(self, coresys): - """Initialize hass object.""" + """Initialize Home Assistant object.""" super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) self.coresys = coresys self.instance = DockerHomeAssistant(coresys) @@ -50,16 +50,16 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): self._access_token_expires = None async def load(self): - """Prepare HomeAssistant object.""" + """Prepare Home Assistant object.""" if await self.instance.attach(): 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() @property def machine(self): - """Return System Machines.""" + """Return the system machines.""" return self.instance.machine @property @@ -69,81 +69,81 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @property def api_ip(self): - """Return IP of HomeAssistant instance.""" + """Return IP of Home Assistant instance.""" return self.sys_docker.network.gateway @property def api_port(self): - """Return network port to home-assistant instance.""" + """Return network port to Home Assistant instance.""" return self._data[ATTR_PORT] @api_port.setter def api_port(self, value): - """Set network port for home-assistant instance.""" + """Set network port for Home Assistant instance.""" self._data[ATTR_PORT] = value @property def api_password(self): - """Return password for home-assistant instance.""" + """Return password for Home Assistant instance.""" return self._data.get(ATTR_PASSWORD) @api_password.setter def api_password(self, value): - """Set password for home-assistant instance.""" + """Set password for Home Assistant instance.""" self._data[ATTR_PASSWORD] = value @property 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] @api_ssl.setter def api_ssl(self, value): - """Set SSL for home-assistant instance.""" + """Set SSL for Home Assistant instance.""" self._data[ATTR_SSL] = value @property def api_url(self): - """Return API url to Home-Assistant.""" + """Return API url to Home Assistant.""" return "{}://{}:{}".format( 'https' if self.api_ssl else 'http', self.api_ip, self.api_port ) @property 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] @watchdog.setter 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 @property 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] @wait_boot.setter 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 @property def version(self): - """Return version of running homeassistant.""" + """Return version of running Home Assistant.""" return self.instance.version @property def last_version(self): - """Return last available version of homeassistant.""" + """Return last available version of Home Assistant.""" if self.is_custom_image: return self._data.get(ATTR_LAST_VERSION) return self.sys_updater.version_homeassistant @last_version.setter def last_version(self, value): - """Set last available version of homeassistant.""" + """Set last available version of Home Assistant.""" if value: self._data[ATTR_LAST_VERSION] = value else: @@ -151,14 +151,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @property def image(self): - """Return image name of hass containter.""" + """Return image name of the Home Assistant container.""" if self._data.get(ATTR_IMAGE): return self._data[ATTR_IMAGE] return os.environ['HOMEASSISTANT_REPOSITORY'] @image.setter def image(self, value): - """Set image name of hass containter.""" + """Set image name of Home Assistant container.""" if value: self._data[ATTR_IMAGE] = value else: @@ -172,27 +172,27 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @property def boot(self): - """Return True if home-assistant boot is enabled.""" + """Return True if Home Assistant boot is enabled.""" return self._data[ATTR_BOOT] @boot.setter def boot(self, value): - """Set home-assistant boot options.""" + """Set Home Assistant boot options.""" self._data[ATTR_BOOT] = value @property def uuid(self): - """Return a UUID of this HomeAssistant.""" + """Return a UUID of this Home Assistant instance.""" return self._data[ATTR_UUID] @property 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) @property def refresh_token(self): - """Return the refresh token to authenticate with HomeAssistant.""" + """Return the refresh token to authenticate with Home Assistant.""" return self._data.get(ATTR_REFRESH_TOKEN) @refresh_token.setter @@ -202,7 +202,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @process_lock async def install_landingpage(self): - """Install a landingpage.""" + """Install a landing page.""" _LOGGER.info("Setup HomeAssistant landingpage") while True: if await self.instance.install('landingpage'): @@ -211,16 +211,16 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): await asyncio.sleep(60) # Run landingpage after installation - _LOGGER.info("Start landingpage") + _LOGGER.info("Start landing page") try: await self._start() except HomeAssistantError: - _LOGGER.warning("Can't start landingpage") + _LOGGER.warning("Can't start landing page") @process_lock async def install(self): - """Install a landingpage.""" - _LOGGER.info("Setup HomeAssistant") + """Install a landing page.""" + _LOGGER.info("Setup Home Assistant") while True: # read homeassistant tag and install it if not self.last_version: @@ -229,18 +229,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): tag = self.last_version if tag and await self.instance.install(tag): break - _LOGGER.warning("Error on install HomeAssistant. Retry in 60sec") + _LOGGER.warning("Error on install Home Assistant. Retry in 60sec") await asyncio.sleep(60) # finishing - _LOGGER.info("HomeAssistant docker now installed") + _LOGGER.info("Home Assistant docker now installed") try: if not self.boot: return - _LOGGER.info("Start HomeAssistant") + _LOGGER.info("Start Home Assistant") await self._start() except HomeAssistantError: - _LOGGER.error("Can't start HomeAssistant!") + _LOGGER.error("Can't start Home Assistant!") finally: await self.instance.cleanup() @@ -260,13 +260,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): async def _update(to_version): """Run Home Assistant update.""" try: - _LOGGER.info("Update HomeAssistant to version %s", to_version) + _LOGGER.info("Update Home Assistant to version %s", to_version) if not await self.instance.update(to_version): raise HomeAssistantUpdateError() finally: if running: await self._start() - _LOGGER.info("Successfull run HomeAssistant %s", to_version) + _LOGGER.info("Successful run Home Assistant %s", to_version) # Update Home Assistant with suppress(HomeAssistantError): @@ -281,9 +281,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): raise HomeAssistantUpdateError() async def _start(self): - """Start HomeAssistant docker & wait.""" + """Start Home Assistant Docker & wait.""" if await self.instance.is_running(): - _LOGGER.warning("HomeAssistant allready running!") + _LOGGER.warning("Home Assistant is already running!") return # Create new API token @@ -296,7 +296,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @process_lock def start(self): - """Run HomeAssistant docker. + """Run Home Assistant docker. Return a coroutine. """ @@ -304,7 +304,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @process_lock def stop(self): - """Stop HomeAssistant docker. + """Stop Home Assistant Docker. Return a coroutine. """ @@ -312,7 +312,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @process_lock async def restart(self): - """Restart HomeAssistant docker.""" + """Restart Home Assistant Docker.""" await self.instance.stop() await self._start() @@ -324,21 +324,21 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return self.instance.logs() def stats(self): - """Return stats of HomeAssistant. + """Return stats of Home Assistant. Return a coroutine. """ return self.instance.stats() def is_running(self): - """Return True if docker container is running. + """Return True if Docker container is running. Return a coroutine. """ return self.instance.is_running() def is_initialize(self): - """Return True if a docker container is exists. + """Return True if a Docker container is exists. Return a coroutine. """ @@ -350,7 +350,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return self.instance.in_progress or self.lock.locked() async def check_config(self): - """Run homeassistant config check.""" + """Run Home Assistant config check.""" result = await self.instance.execute_command( "python3 -m homeassistant -c /config --script check_config" ) @@ -381,10 +381,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): } ) as resp: if resp.status != 200: - _LOGGER.error("Can't update HomeAssistant access token!") + _LOGGER.error("Can't update Home Assistant access token!") raise HomeAssistantAuthError() - _LOGGER.info("Updated HomeAssistant API token") + _LOGGER.info("Updated Home Assistant API token") tokens = await resp.json() self.access_token = tokens['access_token'] self._access_token_expires = \ @@ -429,14 +429,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): raise HomeAssistantAPIError() 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): async with self.make_request('get', 'api/') as resp: if resp.status in (200, 201): return True err = resp.status - _LOGGER.warning("Home-Assistant API config missmatch: %d", err) + _LOGGER.warning("Home Assistant API config mismatch: %d", err) return False async def send_event(self, event_type, event_data=None): @@ -449,7 +449,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return err = resp.status - _LOGGER.warning("HomeAssistant event %s fails: %s", event_type, err) + _LOGGER.warning("Home Assistant event %s fails: %s", event_type, err) return HomeAssistantError() async def _block_till_run(self): @@ -479,13 +479,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): # 1 # Check if Container is is_running if not await self.instance.is_running(): - _LOGGER.error("HomeAssistant is crashed!") + _LOGGER.error("Home Assistant has crashed!") break # 2 # Check if API response if await self.sys_run_in_executor(check_port): - _LOGGER.info("Detect a running HomeAssistant instance") + _LOGGER.info("Detect a running Home Assistant instance") self._error_state = False return @@ -494,17 +494,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): if migration_file.exists(): if not migration_progress: migration_progress = True - _LOGGER.info("HomeAssistant record migration in progress") + _LOGGER.info("Home Assistant record migration in progress") continue elif migration_progress: migration_progress = False # Reset start time start_time = time.monotonic() - _LOGGER.info("HomeAssistant record migration done") + _LOGGER.info("Home Assistant record migration done") # 4 # Timeout 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 self._error_state = True diff --git a/hassio/supervisor.py b/hassio/supervisor.py index aaa467814..9f30a2213 100644 --- a/hassio/supervisor.py +++ b/hassio/supervisor.py @@ -1,4 +1,4 @@ -"""HomeAssistant control object.""" +"""Home Assistant control object.""" import asyncio import logging from pathlib import Path @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) class Supervisor(CoreSysAttributes): - """Hass core object for handle it.""" + """Home Assistant core object for handle it.""" def __init__(self, coresys): """Initialize hass object.""" @@ -23,9 +23,9 @@ class Supervisor(CoreSysAttributes): self.instance = DockerSupervisor(coresys) async def load(self): - """Prepare HomeAssistant object.""" + """Prepare Home Assistant object.""" 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() @property @@ -35,22 +35,22 @@ class Supervisor(CoreSysAttributes): @property def version(self): - """Return version of running homeassistant.""" + """Return version of running Home Assistant.""" return self.instance.version @property def last_version(self): - """Return last available version of homeassistant.""" + """Return last available version of Home Assistant.""" return self.sys_updater.version_hassio @property def image(self): - """Return image name of hass containter.""" + """Return image name of Home Assistant container.""" return self.instance.image @property def arch(self): - """Return arch of hass.io containter.""" + """Return arch of the Hass.io container.""" return self.instance.arch async def update_apparmor(self): @@ -79,20 +79,20 @@ class Supervisor(CoreSysAttributes): _LOGGER.error("Can't update AppArmor profile!") async def update(self, version=None): - """Update HomeAssistant version.""" + """Update Home Assistant version.""" version = version or self.last_version if version == self.sys_supervisor.version: _LOGGER.warning("Version %s is already installed", version) return - _LOGGER.info("Update supervisor to version %s", version) + _LOGGER.info("Update Supervisor to version %s", version) if await self.instance.install(version): await self.update_apparmor() self.sys_loop.call_later(1, self.sys_loop.stop) return True - _LOGGER.error("Update of hass.io fails!") + _LOGGER.error("Update of Hass.io fails!") return False @property diff --git a/hassio/tasks.py b/hassio/tasks.py index f9ae7343f..9ffd6eaed 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -1,4 +1,4 @@ -"""Multible tasks.""" +"""A collection of tasks.""" import asyncio import logging @@ -22,7 +22,7 @@ RUN_WATCHDOG_HOMEASSISTANT_API = 300 class Tasks(CoreSysAttributes): - """Handle Tasks inside HassIO.""" + """Handle Tasks inside Hass.io.""" def __init__(self, coresys): """Initialize Tasks.""" @@ -58,7 +58,7 @@ class Tasks(CoreSysAttributes): _LOGGER.info("All core tasks are scheduled") 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 = [] for addon in self.sys_addons.list_addons: if not addon.is_installed or not addon.auto_update: @@ -71,14 +71,14 @@ class Tasks(CoreSysAttributes): tasks.append(addon.update()) else: _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: - _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) 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: return @@ -91,23 +91,23 @@ class Tasks(CoreSysAttributes): await self.sys_supervisor.update() async def _watchdog_homeassistant_docker(self): - """Check running state of docker and start if they is close.""" - # if Home-Assistant is active + """Check running state of Docker and start if they is close.""" + # if Home Assistant is active if not await self.sys_homeassistant.is_initialize() or \ not self.sys_homeassistant.watchdog or \ self.sys_homeassistant.error_state: return - # if Home-Assistant is running + # if Home Assistant is running if self.sys_homeassistant.in_progress or \ await self.sys_homeassistant.is_running(): 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() 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 a delay in our system. @@ -130,10 +130,10 @@ class Tasks(CoreSysAttributes): retry_scan += 1 if retry_scan == 1: 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 - _LOGGER.error("Watchdog found a problem with Home-Assistant API!") + _LOGGER.error("Watchdog found a problem with Home Assistant API!") try: await self.sys_homeassistant.restart() finally: diff --git a/hassio/updater.py b/hassio/updater.py index 3d81cae58..11031eb7b 100644 --- a/hassio/updater.py +++ b/hassio/updater.py @@ -39,27 +39,27 @@ class Updater(JsonConfig, CoreSysAttributes): @property def version_homeassistant(self): - """Return last version of homeassistant.""" + """Return last version of Home Assistant.""" return self._data.get(ATTR_HOMEASSISTANT) @property def version_hassio(self): - """Return last version of hassio.""" + """Return last version of Hass.io.""" return self._data.get(ATTR_HASSIO) @property def version_hassos(self): - """Return last version of hassos.""" + """Return last version of HassOS.""" return self._data.get(ATTR_HASSOS) @property def version_hassos_cli(self): - """Return last version of hassos cli.""" + """Return last version of HassOS cli.""" return self._data.get(ATTR_HASSOS_CLI) @property def channel(self): - """Return upstream channel of hassio instance.""" + """Return upstream channel of Hass.io instance.""" return self._data[ATTR_CHANNEL] @channel.setter @@ -69,7 +69,7 @@ class Updater(JsonConfig, CoreSysAttributes): @AsyncThrottle(timedelta(seconds=60)) async def fetch_data(self): - """Fetch current versions from github. + """Fetch current versions from Github. Is a coroutine. """ diff --git a/hassio/utils/__init__.py b/hassio/utils/__init__.py index b192b7098..88882ee18 100644 --- a/hassio/utils/__init__.py +++ b/hassio/utils/__init__.py @@ -1,4 +1,4 @@ -"""Tools file for HassIO.""" +"""Tools file for Hass.io.""" from datetime import datetime import hashlib import logging @@ -25,7 +25,8 @@ def process_lock(method): """Return api wrapper.""" if api.lock.locked(): _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 async with api.lock: diff --git a/hassio/utils/apparmor.py b/hassio/utils/apparmor.py index a9137cb06..697579f67 100644 --- a/hassio/utils/apparmor.py +++ b/hassio/utils/apparmor.py @@ -1,4 +1,4 @@ -"""Some functions around apparmor profiles.""" +"""Some functions around AppArmor profiles.""" import logging import re @@ -21,7 +21,7 @@ def get_profile_name(profile_file): continue profiles.add(match.group(1)) except OSError as err: - _LOGGER.error("Can't read apparmor profile: %s", err) + _LOGGER.error("Can't read AppArmor profile: %s", err) raise AppArmorFileError() if len(profiles) != 1: diff --git a/hassio/utils/dt.py b/hassio/utils/dt.py index 5a3a1825d..502456622 100644 --- a/hassio/utils/dt.py +++ b/hassio/utils/dt.py @@ -1,4 +1,4 @@ -"""Tools file for HassIO.""" +"""Tools file for Hass.io.""" from datetime import datetime, timedelta, timezone import logging import re @@ -58,5 +58,5 @@ def parse_datetime(dt_str): def utcnow(): - """Returns current timestamp including timezone.""" + """Return the current timestamp including timezone.""" return datetime.now(UTC) diff --git a/hassio/utils/json.py b/hassio/utils/json.py index 373ff60cd..dc6f8e006 100644 --- a/hassio/utils/json.py +++ b/hassio/utils/json.py @@ -1,4 +1,4 @@ -"""Tools file for HassIO.""" +"""Tools file for Hass.io.""" import json import logging @@ -9,14 +9,14 @@ _LOGGER = logging.getLogger(__name__) def write_json_file(jsonfile, data): - """Write a json file.""" + """Write a JSON file.""" json_str = json.dumps(data, indent=2) with jsonfile.open('w') as conf_file: conf_file.write(json_str) 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: return json.loads(cfile.read()) @@ -33,7 +33,7 @@ class JsonConfig: self.read_data() def reset_data(self): - """Reset json file to default.""" + """Reset JSON file to default.""" try: self._data = self._schema({}) except vol.Invalid as ex: @@ -41,7 +41,7 @@ class JsonConfig: self._file, humanize_error(self._data, ex)) def read_data(self): - """Read json file & validate.""" + """Read JSON file & validate.""" if self._file.is_file(): try: self._data = read_json_file(self._file) @@ -61,7 +61,7 @@ class JsonConfig: self._data = self._schema({}) def save_data(self): - """Store data to config file.""" + """Store data to configuration file.""" # Validate try: self._data = self._schema(self._data) @@ -78,4 +78,5 @@ class JsonConfig: try: write_json_file(self._file, self._data) 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) diff --git a/hassio/validate.py b/hassio/validate.py index b1cf8df84..36dd2fbbe 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -24,7 +24,7 @@ CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV]) def validate_repository(repository): - """Validate a valide repository.""" + """Validate a valid repository.""" data = RE_REPOSITORY.match(repository) if not data: raise vol.Invalid("No valid repository format!") @@ -55,7 +55,7 @@ def validate_timezone(timezone): # pylint: disable=inconsistent-return-statements def convert_to_docker_ports(data): - """Convert data into docker port list.""" + """Convert data into Docker port list.""" # dynamic ports if data is None: return None @@ -72,7 +72,7 @@ def convert_to_docker_ports(data): if isinstance(data, list) and len(data) == 2: 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({ From 9f8ad0547117add3a40f9ea8db36ff7aa28b2a41 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 18 Sep 2018 20:39:58 +0200 Subject: [PATCH 8/8] Add API role system (#703) * Add API role system * Finish * Simplify * Fix lint * Fix rights * Fix lint * Fix spell * Fix log --- API.md | 1 + hassio/addons/addon.py | 7 ++++- hassio/addons/utils.py | 9 +++++- hassio/addons/validate.py | 13 ++++++-- hassio/api/addons.py | 6 ++-- hassio/api/proxy.py | 4 +-- hassio/api/security.py | 63 +++++++++++++++++++++++++++++++-------- hassio/const.py | 6 ++++ 8 files changed, 89 insertions(+), 20 deletions(-) diff --git a/API.md b/API.md index d8deb5cfc..4a9955a03 100644 --- a/API.md +++ b/API.md @@ -483,6 +483,7 @@ Get all available addons. "logo": "bool", "changelog": "bool", "hassio_api": "bool", + "hassio_role": "default|homeassistant|manager|admin", "homeassistant_api": "bool", "full_access": "bool", "protected": "bool", diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 68a3dfcfb..b58ea9404 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -26,7 +26,7 @@ from ..const import ( ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS, - ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, + ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE, SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT) from ..coresys import CoreSysAttributes from ..docker.addon import DockerAddon @@ -376,6 +376,11 @@ class Addon(CoreSysAttributes): """Return True if the add-on access to Home-Assistant api proxy.""" return self._mesh[ATTR_HOMEASSISTANT_API] + @property + def hassio_role(self): + """Return Hass.io role for API.""" + return self._mesh[ATTR_HASSIO_ROLE] + @property def with_stdin(self): """Return True if the add-on access use stdin input.""" diff --git a/hassio/addons/utils.py b/hassio/addons/utils.py index 7c2e4ba31..e937e5bc1 100644 --- a/hassio/addons/utils.py +++ b/hassio/addons/utils.py @@ -6,7 +6,8 @@ import re from ..const import ( SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN, - PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE) + PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE, + ROLE_ADMIN, ROLE_MANAGER) RE_SHA1 = re.compile(r"[a-f0-9]{8}") @@ -36,6 +37,12 @@ def rating_security(addon): PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE): 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 if addon.host_network: rating += -1 diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 43ee5c3cf..794c00d9c 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -19,10 +19,11 @@ from ..const import ( ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED, - ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, + ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE, PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE, - PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE) + PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE, + ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN) from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE _LOGGER = logging.getLogger(__name__) @@ -72,6 +73,13 @@ PRIVILEGED_ALL = [ PRIVILEGED_SYS_PTRACE, ] +ROLE_ALL = [ + ROLE_DEFAULT, + ROLE_HOMEASSISTANT, + ROLE_MANAGER, + ROLE_ADMIN, +] + BASE_IMAGE = { ARCH_ARMHF: "homeassistant/armhf-base:latest", ARCH_AARCH64: "homeassistant/aarch64-base:latest", @@ -121,6 +129,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ vol.Optional(ATTR_GPIO, 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_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL), vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(), vol.Optional(ATTR_STDIN, default=False): vol.Boolean(), vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(), diff --git a/hassio/api/addons.py b/hassio/api/addons.py index af3a3b515..e95ec063a 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -20,8 +20,8 @@ from ..const import ( ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES, ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID, - CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, - REQUEST_FROM) + ATTR_HASSIO_ROLE, + CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM) from ..coresys import CoreSysAttributes from ..validate import DOCKER_PORTS, ALSA_DEVICE from ..exceptions import APINotSupportedError @@ -153,6 +153,7 @@ class APIAddons(CoreSysAttributes): ATTR_WEBUI: addon.webui, ATTR_STDIN: addon.with_stdin, ATTR_HASSIO_API: addon.access_hassio_api, + ATTR_HASSIO_ROLE: addon.hassio_role, ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api, ATTR_GPIO: addon.with_gpio, ATTR_DEVICETREE: addon.with_devicetree, @@ -197,6 +198,7 @@ class APIAddons(CoreSysAttributes): addon = self._extract_addon(request) # Have Access + # REMOVE: don't needed anymore if addon.slug == request[REQUEST_FROM]: _LOGGER.error("Can't self modify his security!") raise APINotSupportedError() diff --git a/hassio/api/proxy.py b/hassio/api/proxy.py index 59ead36b3..526528566 100644 --- a/hassio/api/proxy.py +++ b/hassio/api/proxy.py @@ -25,7 +25,7 @@ class APIProxy(CoreSysAttributes): hassio_token = request.headers.get(HEADER_HA_ACCESS) addon = self.sys_addons.from_token(hassio_token) - # Need removed with 131 + # REMOVE 132 if not addon: addon = self.sys_addons.from_uuid(hassio_token) @@ -184,7 +184,7 @@ class APIProxy(CoreSysAttributes): response.get('access_token')) addon = self.sys_addons.from_token(hassio_token) - # Need removed with 131 + # REMOVE 132 if not addon: addon = self.sys_addons.from_uuid(hassio_token) diff --git a/hassio/api/security.py b/hassio/api/security.py index 2a17b141f..c2b800f71 100644 --- a/hassio/api/security.py +++ b/hassio/api/security.py @@ -5,27 +5,61 @@ import re from aiohttp.web import middleware 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 _LOGGER = logging.getLogger(__name__) +# Free to call or have own security concepts NO_SECURITY_CHECK = re.compile( r"^(?:" - r"|/homeassistant/api/.*$" - r"|/homeassistant/websocket$" - r"|/supervisor/ping$" + r"|/homeassistant/api/.*" + r"|/homeassistant/websocket" + r"|/supervisor/ping" + r"|/services.*" r")$" ) +# Can called by every add-on ADDONS_API_BYPASS = re.compile( r"^(?:" - r"|/homeassistant/info$" - r"|/supervisor/info$" - r"|/addons(?:/self/[^/]+)?$" + r"|/homeassistant/info" + r"|/supervisor/info" + r"|/addons(?:/self/(?!security)[^/]+)?" 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): """Security middleware functions.""" @@ -66,17 +100,22 @@ class SecurityMiddleware(CoreSysAttributes): addon = None if hassio_token and not request_from: addon = self.sys_addons.from_token(hassio_token) - # Need removed with 131 + # REMOVE 132 if not addon: addon = self.sys_addons.from_uuid(hassio_token) # Check Add-on API access - if addon and addon.access_hassio_api: - _LOGGER.info("%s access from %s", request.path, addon.slug) - request_from = addon.slug - elif addon and ADDONS_API_BYPASS.match(request.path): + if addon and ADDONS_API_BYPASS.match(request.path): _LOGGER.debug("Passthrough %s from %s", request.path, 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: request[REQUEST_FROM] = request_from diff --git a/hassio/const.py b/hassio/const.py index 7965b4694..abc3a2aac 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -184,6 +184,7 @@ ATTR_DOCKER_API = 'docker_api' ATTR_FULL_ACCESS = 'full_access' ATTR_PROTECTED = 'protected' ATTR_RATING = 'rating' +ATTR_HASSIO_ROLE = 'hassio_role' SERVICE_MQTT = 'mqtt' @@ -246,3 +247,8 @@ FEATURES_REBOOT = 'reboot' FEATURES_HASSOS = 'hassos' FEATURES_HOSTNAME = 'hostname' FEATURES_SERVICES = 'services' + +ROLE_DEFAULT = 'default' +ROLE_HOMEASSISTANT = 'homeassistant' +ROLE_MANAGER = 'manager' +ROLE_ADMIN = 'admin'