mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-02 12:50:21 +00:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1dc9f35e12 | ||
![]() |
051b63c7cc | ||
![]() |
aac4b9b24a | ||
![]() |
1a208a20b6 | ||
![]() |
b1e8722ead | ||
![]() |
a66af6e903 | ||
![]() |
0c345fc615 | ||
![]() |
087b082a6b | ||
![]() |
0b85209eae | ||
![]() |
d81bc7de46 | ||
![]() |
e3a99b9f89 | ||
![]() |
5d319b37ea | ||
![]() |
9f25606986 | ||
![]() |
ecd12732ee | ||
![]() |
85fbde8e36 | ||
![]() |
6e6c2c3efb | ||
![]() |
0d4a808449 | ||
![]() |
087f746647 | ||
![]() |
640d66ad1a | ||
![]() |
f5f5ed83af | ||
![]() |
95f01a1161 | ||
![]() |
b84e7e7d94 | ||
![]() |
5d7018f3f0 | ||
![]() |
d87a85ceb5 | ||
![]() |
9ab6e80b6f | ||
![]() |
78e91e859e | ||
![]() |
9eee8eade6 | ||
![]() |
124ce0b8b7 | ||
![]() |
00e7d96472 | ||
![]() |
398815efd8 | ||
![]() |
bdc2bdcf56 | ||
![]() |
68eafb0a7d |
5
API.md
5
API.md
@@ -478,10 +478,14 @@ Get all available addons.
|
|||||||
"changelog": "bool",
|
"changelog": "bool",
|
||||||
"hassio_api": "bool",
|
"hassio_api": "bool",
|
||||||
"homeassistant_api": "bool",
|
"homeassistant_api": "bool",
|
||||||
|
"full_access": "bool",
|
||||||
|
"protected": "bool",
|
||||||
|
"rating": "1-6",
|
||||||
"stdin": "bool",
|
"stdin": "bool",
|
||||||
"webui": "null|http(s)://[HOST]:port/xy/zx",
|
"webui": "null|http(s)://[HOST]:port/xy/zx",
|
||||||
"gpio": "bool",
|
"gpio": "bool",
|
||||||
"devicetree": "bool",
|
"devicetree": "bool",
|
||||||
|
"docker_api": "bool",
|
||||||
"audio": "bool",
|
"audio": "bool",
|
||||||
"audio_input": "null|0,0",
|
"audio_input": "null|0,0",
|
||||||
"audio_output": "null|0,0",
|
"audio_output": "null|0,0",
|
||||||
@@ -506,6 +510,7 @@ Get all available addons.
|
|||||||
"CONTAINER": "port|[ip, port]"
|
"CONTAINER": "port|[ip, port]"
|
||||||
},
|
},
|
||||||
"options": {},
|
"options": {},
|
||||||
|
"protected": "bool",
|
||||||
"audio_output": "null|0,0",
|
"audio_output": "null|0,0",
|
||||||
"audio_input": "null|0,0"
|
"audio_input": "null|0,0"
|
||||||
}
|
}
|
||||||
|
18
Dockerfile
18
Dockerfile
@@ -1,15 +1,17 @@
|
|||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
# Setup base
|
# Install base
|
||||||
COPY requirements.txt /usr/src/
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
git \
|
git \
|
||||||
socat \
|
socat \
|
||||||
glib \
|
glib \
|
||||||
libstdc++ \
|
libstdc++ \
|
||||||
eudev-libs \
|
eudev-libs
|
||||||
&& apk add --no-cache --virtual .build-dependencies \
|
|
||||||
|
# Install requirements
|
||||||
|
COPY requirements.txt /usr/src/
|
||||||
|
RUN apk add --no-cache --virtual .build-dependencies \
|
||||||
make \
|
make \
|
||||||
g++ \
|
g++ \
|
||||||
&& pip3 install --no-cache-dir -r /usr/src/requirements.txt \
|
&& pip3 install --no-cache-dir -r /usr/src/requirements.txt \
|
||||||
|
@@ -25,8 +25,9 @@ from ..const import (
|
|||||||
ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
|
ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
|
||||||
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, SECURITY_PROFILE, SECURITY_DISABLE,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
||||||
SECURITY_DEFAULT)
|
ATTR_PROTECTED,
|
||||||
|
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.addon import DockerAddon
|
from ..docker.addon import DockerAddon
|
||||||
from ..utils.json import write_json_file, read_json_file
|
from ..utils.json import write_json_file, read_json_file
|
||||||
@@ -201,6 +202,18 @@ class Addon(CoreSysAttributes):
|
|||||||
return self._data.cache[self._id][ATTR_VERSION]
|
return self._data.cache[self._id][ATTR_VERSION]
|
||||||
return self.version_installed
|
return self.version_installed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def protected(self):
|
||||||
|
"""Return if addon is in protected mode."""
|
||||||
|
if self.is_installed:
|
||||||
|
return self._data.user[self._id][ATTR_PROTECTED]
|
||||||
|
return True
|
||||||
|
|
||||||
|
@protected.setter
|
||||||
|
def protected(self, value):
|
||||||
|
"""Set addon in protected mode."""
|
||||||
|
self._data.user[self._id][ATTR_PROTECTED] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def startup(self):
|
def startup(self):
|
||||||
"""Return startup type of addon."""
|
"""Return startup type of addon."""
|
||||||
@@ -335,6 +348,11 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return if the add-on don't support hass labels."""
|
"""Return if the add-on don't support hass labels."""
|
||||||
return self._mesh.get(ATTR_LEGACY)
|
return self._mesh.get(ATTR_LEGACY)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def access_docker_api(self):
|
||||||
|
"""Return if the add-on need read-only docker API access."""
|
||||||
|
return self._mesh.get(ATTR_DOCKER_API)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_hassio_api(self):
|
def access_hassio_api(self):
|
||||||
"""Return True if the add-on access to hassio api."""
|
"""Return True if the add-on access to hassio api."""
|
||||||
@@ -355,6 +373,11 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return True if the add-on access to gpio interface."""
|
"""Return True if the add-on access to gpio interface."""
|
||||||
return self._mesh[ATTR_GPIO]
|
return self._mesh[ATTR_GPIO]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_full_access(self):
|
||||||
|
"""Return True if the add-on want full access to hardware."""
|
||||||
|
return self._mesh[ATTR_FULL_ACCESS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_devicetree(self):
|
def with_devicetree(self):
|
||||||
"""Return True if the add-on read access to devicetree."""
|
"""Return True if the add-on read access to devicetree."""
|
||||||
|
@@ -4,11 +4,53 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN,
|
||||||
|
PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO)
|
||||||
|
|
||||||
RE_SHA1 = re.compile(r"[a-f0-9]{8}")
|
RE_SHA1 = re.compile(r"[a-f0-9]{8}")
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def rating_security(addon):
|
||||||
|
"""Return 1-5 for security rating.
|
||||||
|
|
||||||
|
1 = not secure
|
||||||
|
5 = high secure
|
||||||
|
"""
|
||||||
|
rating = 5
|
||||||
|
|
||||||
|
# AppArmor
|
||||||
|
if addon.apparmor == SECURITY_DISABLE:
|
||||||
|
rating += -1
|
||||||
|
elif addon.apparmor == SECURITY_PROFILE:
|
||||||
|
rating += 1
|
||||||
|
|
||||||
|
# API Access
|
||||||
|
if addon.access_hassio_api or addon.access_homeassistant_api:
|
||||||
|
rating += -1
|
||||||
|
|
||||||
|
# Privileged options
|
||||||
|
if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
|
||||||
|
PRIVILEGED_SYS_RAWIO):
|
||||||
|
rating += -1
|
||||||
|
|
||||||
|
# Not secure Networking
|
||||||
|
if addon.host_network:
|
||||||
|
rating += -1
|
||||||
|
|
||||||
|
# Full Access
|
||||||
|
if addon.with_full_access:
|
||||||
|
rating += -2
|
||||||
|
|
||||||
|
# Docker Access
|
||||||
|
if addon.access_docker_api:
|
||||||
|
rating = 1
|
||||||
|
|
||||||
|
return max(min(6, rating), 1)
|
||||||
|
|
||||||
|
|
||||||
def get_hash_from_repository(name):
|
def get_hash_from_repository(name):
|
||||||
"""Generate a hash from repository."""
|
"""Generate a hash from repository."""
|
||||||
key = name.lower().encode()
|
key = name.lower().encode()
|
||||||
|
@@ -18,7 +18,11 @@ from ..const import (
|
|||||||
ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH,
|
ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH,
|
||||||
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_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
||||||
|
ATTR_FULL_ACCESS,
|
||||||
|
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
||||||
|
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
||||||
|
PRIVILEGED_SYS_RESOURCE)
|
||||||
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__)
|
||||||
@@ -58,12 +62,13 @@ STARTUP_ALL = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
PRIVILEGED_ALL = [
|
PRIVILEGED_ALL = [
|
||||||
"NET_ADMIN",
|
PRIVILEGED_NET_ADMIN,
|
||||||
"SYS_ADMIN",
|
PRIVILEGED_SYS_ADMIN,
|
||||||
"SYS_RAWIO",
|
PRIVILEGED_SYS_RAWIO,
|
||||||
"IPC_LOCK",
|
PRIVILEGED_IPC_LOCK,
|
||||||
"SYS_TIME",
|
PRIVILEGED_SYS_TIME,
|
||||||
"SYS_NICE"
|
PRIVILEGED_SYS_NICE,
|
||||||
|
PRIVILEGED_SYS_RESOURCE,
|
||||||
]
|
]
|
||||||
|
|
||||||
BASE_IMAGE = {
|
BASE_IMAGE = {
|
||||||
@@ -109,6 +114,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
||||||
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
||||||
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
|
vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_FULL_ACCESS, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
||||||
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(),
|
||||||
@@ -116,6 +122,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
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(),
|
||||||
|
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
||||||
vol.Optional(ATTR_DISCOVERY): [vol.Match(RE_DISCOVERY)],
|
vol.Optional(ATTR_DISCOVERY): [vol.Match(RE_DISCOVERY)],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
@@ -168,6 +175,7 @@ SCHEMA_ADDON_USER = vol.Schema({
|
|||||||
vol.Optional(ATTR_NETWORK): DOCKER_PORTS,
|
vol.Optional(ATTR_NETWORK): DOCKER_PORTS,
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
||||||
|
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ import voluptuous as vol
|
|||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
from ..addons.utils import rating_security
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS,
|
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS,
|
||||||
ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY,
|
ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY,
|
||||||
@@ -17,10 +18,12 @@ from ..const import (
|
|||||||
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION,
|
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION,
|
||||||
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_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT)
|
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING,
|
||||||
|
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
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ SCHEMA_OPTIONS = vol.Schema({
|
|||||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
||||||
|
vol.Optional(ATTR_PROTECTED): vol.Boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -116,6 +120,8 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_LAST_VERSION: addon.last_version,
|
ATTR_LAST_VERSION: addon.last_version,
|
||||||
ATTR_STATE: await addon.state(),
|
ATTR_STATE: await addon.state(),
|
||||||
|
ATTR_PROTECTED: addon.protected,
|
||||||
|
ATTR_RATING: rating_security(addon),
|
||||||
ATTR_BOOT: addon.boot,
|
ATTR_BOOT: addon.boot,
|
||||||
ATTR_OPTIONS: addon.options,
|
ATTR_OPTIONS: addon.options,
|
||||||
ATTR_URL: addon.url,
|
ATTR_URL: addon.url,
|
||||||
@@ -126,6 +132,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
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,
|
||||||
|
ATTR_FULL_ACCESS: addon.with_full_access,
|
||||||
ATTR_APPARMOR: addon.apparmor,
|
ATTR_APPARMOR: addon.apparmor,
|
||||||
ATTR_DEVICES: self._pretty_devices(addon),
|
ATTR_DEVICES: self._pretty_devices(addon),
|
||||||
ATTR_ICON: addon.with_icon,
|
ATTR_ICON: addon.with_icon,
|
||||||
@@ -137,6 +144,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
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,
|
||||||
|
ATTR_DOCKER_API: addon.access_docker_api,
|
||||||
ATTR_AUDIO: addon.with_audio,
|
ATTR_AUDIO: addon.with_audio,
|
||||||
ATTR_AUDIO_INPUT: addon.audio_input,
|
ATTR_AUDIO_INPUT: addon.audio_input,
|
||||||
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
||||||
@@ -149,6 +157,11 @@ class APIAddons(CoreSysAttributes):
|
|||||||
"""Store user options for addon."""
|
"""Store user options for addon."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
|
# Have Access
|
||||||
|
if addon.slug == request[REQUEST_FROM]:
|
||||||
|
_LOGGER.error("Add-on can't self modify his options!")
|
||||||
|
raise APINotSupportedError()
|
||||||
|
|
||||||
addon_schema = SCHEMA_OPTIONS.extend({
|
addon_schema = SCHEMA_OPTIONS.extend({
|
||||||
vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema),
|
vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema),
|
||||||
})
|
})
|
||||||
@@ -167,6 +180,9 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon.audio_input = body[ATTR_AUDIO_INPUT]
|
addon.audio_input = body[ATTR_AUDIO_INPUT]
|
||||||
if ATTR_AUDIO_OUTPUT in body:
|
if ATTR_AUDIO_OUTPUT in body:
|
||||||
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||||
|
if ATTR_PROTECTED in body:
|
||||||
|
_LOGGER.warning("Protected flag changing for %s!", addon.slug)
|
||||||
|
addon.protected = body[ATTR_PROTECTED]
|
||||||
|
|
||||||
addon.save_data()
|
addon.save_data()
|
||||||
return True
|
return True
|
||||||
|
@@ -21,17 +21,16 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SCHEMA_OPTIONS = vol.Schema({
|
SCHEMA_OPTIONS = vol.Schema({
|
||||||
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
||||||
vol.Any(None, vol.Coerce(str)),
|
vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
||||||
vol.Any(None, DOCKER_IMAGE),
|
vol.Any(None, DOCKER_IMAGE),
|
||||||
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_SSL): vol.Boolean(),
|
vol.Optional(ATTR_SSL): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WAIT_BOOT):
|
vol.Optional(ATTR_WAIT_BOOT):
|
||||||
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
||||||
# Required once we enforce user system
|
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_REFRESH_TOKEN): str,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({
|
||||||
|
@@ -59,6 +59,8 @@ class APIProxy(CoreSysAttributes):
|
|||||||
|
|
||||||
except HomeAssistantAuthError:
|
except HomeAssistantAuthError:
|
||||||
_LOGGER.error("Authenticate error on API for request %s", path)
|
_LOGGER.error("Authenticate error on API for request %s", path)
|
||||||
|
except HomeAssistantAPIError:
|
||||||
|
_LOGGER.error("Error on API for request %s", path)
|
||||||
except aiohttp.ClientError as err:
|
except aiohttp.ClientError as err:
|
||||||
_LOGGER.error("Client error on API %s request %s", path, err)
|
_LOGGER.error("Client error on API %s request %s", path, err)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
@@ -148,11 +150,12 @@ class APIProxy(CoreSysAttributes):
|
|||||||
self.sys_homeassistant.access_token = None
|
self.sys_homeassistant.access_token = None
|
||||||
return await self._websocket_client()
|
return await self._websocket_client()
|
||||||
|
|
||||||
_LOGGER.error(
|
raise HomeAssistantAuthError()
|
||||||
"Failed authentication to Home-Assistant websocket: %s", data)
|
|
||||||
|
|
||||||
except (RuntimeError, HomeAssistantAPIError) as err:
|
except (RuntimeError, ValueError) as err:
|
||||||
_LOGGER.error("Client error on websocket API %s.", err)
|
_LOGGER.error("Client error on websocket API %s.", err)
|
||||||
|
except HomeAssistantAuthError as err:
|
||||||
|
_LOGGER.error("Failed authentication to HomeAssistant websocket")
|
||||||
|
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
|
|
||||||
|
@@ -30,10 +30,10 @@ def api_process(method):
|
|||||||
"""Return api information."""
|
"""Return api information."""
|
||||||
try:
|
try:
|
||||||
answer = await method(api, *args, **kwargs)
|
answer = await method(api, *args, **kwargs)
|
||||||
except RuntimeError as err:
|
|
||||||
return api_return_error(message=str(err))
|
|
||||||
except HassioError:
|
except HassioError:
|
||||||
return api_return_error()
|
return api_return_error()
|
||||||
|
except RuntimeError as err:
|
||||||
|
return api_return_error(message=str(err))
|
||||||
|
|
||||||
if isinstance(answer, dict):
|
if isinstance(answer, dict):
|
||||||
return api_return_ok(data=answer)
|
return api_return_ok(data=answer)
|
||||||
|
@@ -66,10 +66,11 @@ def initialize_system_data(coresys):
|
|||||||
config = coresys.config
|
config = coresys.config
|
||||||
|
|
||||||
# homeassistant config folder
|
# homeassistant config folder
|
||||||
if not config.path_config.is_dir():
|
if not config.path_homeassistant.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Create Home-Assistant config folder %s", config.path_config)
|
"Create Home-Assistant config folder %s",
|
||||||
config.path_config.mkdir()
|
config.path_homeassistant)
|
||||||
|
config.path_homeassistant.mkdir()
|
||||||
|
|
||||||
# hassio ssl folder
|
# hassio ssl folder
|
||||||
if not config.path_ssl.is_dir():
|
if not config.path_ssl.is_dir():
|
||||||
|
@@ -2,8 +2,11 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST,
|
FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST,
|
||||||
ATTR_LAST_BOOT, ATTR_WAIT_BOOT)
|
ATTR_LAST_BOOT, ATTR_WAIT_BOOT)
|
||||||
@@ -29,6 +32,8 @@ APPARMOR_DATA = PurePath("apparmor")
|
|||||||
|
|
||||||
DEFAULT_BOOT_TIME = datetime.utcfromtimestamp(0).isoformat()
|
DEFAULT_BOOT_TIME = datetime.utcfromtimestamp(0).isoformat()
|
||||||
|
|
||||||
|
RE_TIMEZONE = re.compile(r"time_zone: (?P<timezone>[\w/\-+]+)")
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(JsonConfig):
|
class CoreConfig(JsonConfig):
|
||||||
"""Hold all core config data."""
|
"""Hold all core config data."""
|
||||||
@@ -40,7 +45,21 @@ class CoreConfig(JsonConfig):
|
|||||||
@property
|
@property
|
||||||
def timezone(self):
|
def timezone(self):
|
||||||
"""Return system timezone."""
|
"""Return system timezone."""
|
||||||
return self._data[ATTR_TIMEZONE]
|
config_file = Path(self.path_homeassistant, 'configuration.yaml')
|
||||||
|
try:
|
||||||
|
assert config_file.exists()
|
||||||
|
configuration = config_file.read_text()
|
||||||
|
|
||||||
|
data = RE_TIMEZONE.search(configuration)
|
||||||
|
assert data
|
||||||
|
|
||||||
|
timezone = data.group('timezone')
|
||||||
|
pytz.timezone(timezone)
|
||||||
|
except (pytz.exceptions.UnknownTimeZoneError, OSError, AssertionError):
|
||||||
|
_LOGGER.debug("Can't parse HomeAssistant timezone")
|
||||||
|
return self._data[ATTR_TIMEZONE]
|
||||||
|
|
||||||
|
return timezone
|
||||||
|
|
||||||
@timezone.setter
|
@timezone.setter
|
||||||
def timezone(self, value):
|
def timezone(self, value):
|
||||||
@@ -83,12 +102,12 @@ class CoreConfig(JsonConfig):
|
|||||||
return PurePath(os.environ['SUPERVISOR_SHARE'])
|
return PurePath(os.environ['SUPERVISOR_SHARE'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_config(self):
|
def path_extern_homeassistant(self):
|
||||||
"""Return config path extern for docker."""
|
"""Return config path extern for docker."""
|
||||||
return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG))
|
return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_config(self):
|
def path_homeassistant(self):
|
||||||
"""Return config path inside supervisor."""
|
"""Return config path inside supervisor."""
|
||||||
return Path(HASSIO_DATA, HOMEASSISTANT_CONFIG)
|
return Path(HASSIO_DATA, HOMEASSISTANT_CONFIG)
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '123'
|
HASSIO_VERSION = '128'
|
||||||
|
|
||||||
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 = \
|
||||||
@@ -178,6 +178,10 @@ ATTR_HASSOS_CLI = 'hassos_cli'
|
|||||||
ATTR_VERSION_CLI = 'version_cli'
|
ATTR_VERSION_CLI = 'version_cli'
|
||||||
ATTR_VERSION_CLI_LATEST = 'version_cli_latest'
|
ATTR_VERSION_CLI_LATEST = 'version_cli_latest'
|
||||||
ATTR_REFRESH_TOKEN = 'refresh_token'
|
ATTR_REFRESH_TOKEN = 'refresh_token'
|
||||||
|
ATTR_DOCKER_API = 'docker_api'
|
||||||
|
ATTR_FULL_ACCESS = 'full_access'
|
||||||
|
ATTR_PROTECTED = 'protected'
|
||||||
|
ATTR_RATING = 'rating'
|
||||||
|
|
||||||
SERVICE_MQTT = 'mqtt'
|
SERVICE_MQTT = 'mqtt'
|
||||||
|
|
||||||
@@ -226,6 +230,14 @@ SECURITY_PROFILE = 'profile'
|
|||||||
SECURITY_DEFAULT = 'default'
|
SECURITY_DEFAULT = 'default'
|
||||||
SECURITY_DISABLE = 'disable'
|
SECURITY_DISABLE = 'disable'
|
||||||
|
|
||||||
|
PRIVILEGED_NET_ADMIN = 'NET_ADMIN'
|
||||||
|
PRIVILEGED_SYS_ADMIN = 'SYS_ADMIN'
|
||||||
|
PRIVILEGED_SYS_RAWIO = 'SYS_RAWIO'
|
||||||
|
PRIVILEGED_IPC_LOCK = 'IPC_LOCK'
|
||||||
|
PRIVILEGED_SYS_TIME = 'SYS_TIME'
|
||||||
|
PRIVILEGED_SYS_NICE = 'SYS_NICE'
|
||||||
|
PRIVILEGED_SYS_RESOURCE = 'SYS_RESOURCE'
|
||||||
|
|
||||||
FEATURES_SHUTDOWN = 'shutdown'
|
FEATURES_SHUTDOWN = 'shutdown'
|
||||||
FEATURES_REBOOT = 'reboot'
|
FEATURES_REBOOT = 'reboot'
|
||||||
FEATURES_HASSOS = 'hassos'
|
FEATURES_HASSOS = 'hassos'
|
||||||
|
@@ -66,6 +66,11 @@ class CoreSys:
|
|||||||
"""Return True if we run dev modus."""
|
"""Return True if we run dev modus."""
|
||||||
return self._updater.channel == CHANNEL_DEV
|
return self._updater.channel == CHANNEL_DEV
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timezone(self):
|
||||||
|
"""Return timezone."""
|
||||||
|
return self._config.timezone
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""Return loop object."""
|
"""Return loop object."""
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
"""Init file for HassIO addon docker object."""
|
"""Init file for HassIO addon docker object."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
import requests
|
import requests
|
||||||
@@ -66,6 +67,11 @@ class DockerAddon(DockerInterface):
|
|||||||
return 'host'
|
return 'host'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_access(self):
|
||||||
|
"""Return True if full access is enabled."""
|
||||||
|
return not self.addon.protected and self.addon.with_full_access
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self):
|
def hostname(self):
|
||||||
"""Return slug/id of addon."""
|
"""Return slug/id of addon."""
|
||||||
@@ -85,7 +91,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
**addon_env,
|
**addon_env,
|
||||||
ENV_TIME: self.sys_config.timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
ENV_TOKEN: self.addon.uuid,
|
ENV_TOKEN: self.addon.uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +178,7 @@ class DockerAddon(DockerInterface):
|
|||||||
# setup config mappings
|
# setup config mappings
|
||||||
if MAP_CONFIG in addon_mapping:
|
if MAP_CONFIG in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.sys_config.path_extern_config): {
|
str(self.sys_config.path_extern_homeassistant): {
|
||||||
'bind': "/config", 'mode': addon_mapping[MAP_CONFIG]
|
'bind': "/config", 'mode': addon_mapping[MAP_CONFIG]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
@@ -204,14 +210,14 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
# GPIO support
|
# GPIO support
|
||||||
if self.addon.with_gpio:
|
if self.addon.with_gpio:
|
||||||
volumes.update({
|
for gpio_path in ("/sys/class/gpio", "/sys/devices/platform/soc"):
|
||||||
"/sys/class/gpio": {
|
if not Path(gpio_path).exists():
|
||||||
'bind': "/sys/class/gpio", 'mode': 'rw'
|
continue
|
||||||
},
|
volumes.update({
|
||||||
"/sys/devices/platform/soc": {
|
gpio_path: {
|
||||||
'bind': "/sys/devices/platform/soc", 'mode': 'rw'
|
'bind': gpio_path, 'mode': 'rw'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# DeviceTree support
|
# DeviceTree support
|
||||||
if self.addon.with_devicetree:
|
if self.addon.with_devicetree:
|
||||||
@@ -221,6 +227,14 @@ class DockerAddon(DockerInterface):
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Docker API support
|
||||||
|
if not self.addon.protected and self.addon.access_docker_api:
|
||||||
|
volumes.update({
|
||||||
|
"/var/run/docker.sock": {
|
||||||
|
'bind': "/var/run/docker.sock", 'mode': 'ro'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
# Host dbus system
|
# Host dbus system
|
||||||
if self.addon.host_dbus:
|
if self.addon.host_dbus:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
@@ -245,6 +259,11 @@ class DockerAddon(DockerInterface):
|
|||||||
if self._is_running():
|
if self._is_running():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Security check
|
||||||
|
if not self.addon.protected:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s run with disabled proteced mode!", self.addon.name)
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
@@ -254,6 +273,7 @@ class DockerAddon(DockerInterface):
|
|||||||
hostname=self.hostname,
|
hostname=self.hostname,
|
||||||
detach=True,
|
detach=True,
|
||||||
init=True,
|
init=True,
|
||||||
|
privileged=self.full_access,
|
||||||
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,
|
||||||
|
@@ -61,11 +61,11 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
network_mode='host',
|
network_mode='host',
|
||||||
environment={
|
environment={
|
||||||
'HASSIO': self.sys_docker.network.supervisor,
|
'HASSIO': self.sys_docker.network.supervisor,
|
||||||
ENV_TIME: self.sys_config.timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
ENV_TOKEN: self.sys_homeassistant.uuid,
|
ENV_TOKEN: self.sys_homeassistant.uuid,
|
||||||
},
|
},
|
||||||
volumes={
|
volumes={
|
||||||
str(self.sys_config.path_extern_config):
|
str(self.sys_config.path_extern_homeassistant):
|
||||||
{'bind': '/config', 'mode': 'rw'},
|
{'bind': '/config', 'mode': 'rw'},
|
||||||
str(self.sys_config.path_extern_ssl):
|
str(self.sys_config.path_extern_ssl):
|
||||||
{'bind': '/ssl', 'mode': 'ro'},
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
@@ -95,13 +95,15 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
stdout=True,
|
stdout=True,
|
||||||
stderr=True,
|
stderr=True,
|
||||||
environment={
|
environment={
|
||||||
ENV_TIME: self.sys_config.timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
},
|
},
|
||||||
volumes={
|
volumes={
|
||||||
str(self.sys_config.path_extern_config):
|
str(self.sys_config.path_extern_homeassistant):
|
||||||
{'bind': '/config', 'mode': 'rw'},
|
{'bind': '/config', 'mode': 'rw'},
|
||||||
str(self.sys_config.path_extern_ssl):
|
str(self.sys_config.path_extern_ssl):
|
||||||
{'bind': '/ssl', 'mode': 'ro'},
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
|
str(self.sys_config.path_extern_share):
|
||||||
|
{'bind': '/share', 'mode': 'ro'},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
"""Core Exceptions."""
|
"""Core Exceptions."""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
class HassioError(Exception):
|
class HassioError(Exception):
|
||||||
@@ -26,14 +23,13 @@ class HomeAssistantUpdateError(HomeAssistantError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantAuthError(HomeAssistantError):
|
class HomeAssistantAPIError(HomeAssistantError):
|
||||||
"""Home Assistant Auth API exception."""
|
"""Home Assistant API exception."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantAPIError(
|
class HomeAssistantAuthError(HomeAssistantAPIError):
|
||||||
HomeAssistantAuthError, asyncio.TimeoutError, aiohttp.ClientError):
|
"""Home Assistant Auth API exception."""
|
||||||
"""Home Assistant API exception."""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -80,6 +76,19 @@ class HostServiceError(HostError):
|
|||||||
|
|
||||||
class HostAppArmorError(HostError):
|
class HostAppArmorError(HostError):
|
||||||
"""Host apparmor functions fails."""
|
"""Host apparmor functions fails."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
class APIError(HassioError):
|
||||||
|
"""API errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class APINotSupportedError(HassioNotSupportedError):
|
||||||
|
"""API not supported error."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# utils/gdbus
|
# utils/gdbus
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
"""HomeAssistant control object."""
|
"""HomeAssistant control object."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import asynccontextmanager, suppress
|
from contextlib import asynccontextmanager, suppress
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
self._error_state = False
|
self._error_state = False
|
||||||
# We don't persist access tokens. Instead we fetch new ones when needed
|
# We don't persist access tokens. Instead we fetch new ones when needed
|
||||||
self.access_token = None
|
self.access_token = None
|
||||||
|
self._access_token_expires = None
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Prepare HomeAssistant object."""
|
"""Prepare HomeAssistant object."""
|
||||||
@@ -351,11 +354,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def ensure_access_token(self):
|
async def ensure_access_token(self):
|
||||||
"""Ensures there is an access token."""
|
"""Ensures there is an access token."""
|
||||||
if self.access_token is not None:
|
if (self.access_token is not None and
|
||||||
|
self._access_token_expires > datetime.utcnow()):
|
||||||
return
|
return
|
||||||
|
|
||||||
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
||||||
async with self.sys_websession_ssl.get(
|
async with self.sys_websession_ssl.post(
|
||||||
f"{self.api_url}/auth/token",
|
f"{self.api_url}/auth/token",
|
||||||
timeout=30,
|
timeout=30,
|
||||||
data={
|
data={
|
||||||
@@ -364,14 +368,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
) as resp:
|
) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
_LOGGER.error("Authenticate problem with HomeAssistant!")
|
_LOGGER.error("Can't update HomeAssistant access token!")
|
||||||
raise HomeAssistantAuthError()
|
raise HomeAssistantAuthError()
|
||||||
|
|
||||||
|
_LOGGER.info("Updated HomeAssistant API token")
|
||||||
tokens = await resp.json()
|
tokens = await resp.json()
|
||||||
self.access_token = tokens['access_token']
|
self.access_token = tokens['access_token']
|
||||||
return
|
self._access_token_expires = \
|
||||||
|
datetime.utcnow() + timedelta(seconds=tokens['expires_in'])
|
||||||
_LOGGER.error("Can't update HomeAssistant access token!")
|
|
||||||
raise HomeAssistantAPIError()
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def make_request(self, method, path, json=None, content_type=None,
|
async def make_request(self, method, path, json=None, content_type=None,
|
||||||
@@ -394,15 +398,20 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
await self.ensure_access_token()
|
await self.ensure_access_token()
|
||||||
headers[hdrs.AUTHORIZATION] = f'Bearer {self.access_token}'
|
headers[hdrs.AUTHORIZATION] = f'Bearer {self.access_token}'
|
||||||
|
|
||||||
async with getattr(self.sys_websession_ssl, method)(
|
try:
|
||||||
url, data=data, timeout=timeout, json=json, headers=headers
|
async with getattr(self.sys_websession_ssl, method)(
|
||||||
) as resp:
|
url, data=data, timeout=timeout, json=json,
|
||||||
# Access token expired
|
headers=headers
|
||||||
if resp.status == 401 and self.refresh_token:
|
) as resp:
|
||||||
self.access_token = None
|
# Access token expired
|
||||||
continue
|
if resp.status == 401 and self.refresh_token:
|
||||||
yield resp
|
self.access_token = None
|
||||||
return
|
continue
|
||||||
|
yield resp
|
||||||
|
return
|
||||||
|
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||||
|
_LOGGER.error("Error on call %s: %s", url, err)
|
||||||
|
break
|
||||||
|
|
||||||
raise HomeAssistantAPIError()
|
raise HomeAssistantAPIError()
|
||||||
|
|
||||||
@@ -433,6 +442,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
async def _block_till_run(self):
|
async def _block_till_run(self):
|
||||||
"""Block until Home-Assistant is booting up or startup timeout."""
|
"""Block until Home-Assistant is booting up or startup timeout."""
|
||||||
start_time = time.monotonic()
|
start_time = time.monotonic()
|
||||||
|
migration_progress = False
|
||||||
|
migration_file = Path(
|
||||||
|
self.sys_config.path_homeassistant, '.migration_progress')
|
||||||
|
|
||||||
def check_port():
|
def check_port():
|
||||||
"""Check if port is mapped."""
|
"""Check if port is mapped."""
|
||||||
@@ -448,21 +460,39 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
while time.monotonic() - start_time < self.wait_boot:
|
while True:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
# 1
|
||||||
|
# Check if Container is is_running
|
||||||
|
if not await self.instance.is_running():
|
||||||
|
_LOGGER.error("HomeAssistant is crashed!")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 2
|
||||||
# Check if API response
|
# Check if API response
|
||||||
if await self.sys_run_in_executor(check_port):
|
if await self.sys_run_in_executor(check_port):
|
||||||
_LOGGER.info("Detect a running HomeAssistant instance")
|
_LOGGER.info("Detect a running HomeAssistant instance")
|
||||||
self._error_state = False
|
self._error_state = False
|
||||||
return
|
return
|
||||||
|
|
||||||
# wait and don't hit the system
|
# 3
|
||||||
await asyncio.sleep(10)
|
# Running DB Migration
|
||||||
|
if migration_file.exists():
|
||||||
|
if not migration_progress:
|
||||||
|
migration_progress = True
|
||||||
|
_LOGGER.info("HomeAssistant record migration in progress")
|
||||||
|
continue
|
||||||
|
elif migration_progress:
|
||||||
|
migration_progress = False # Reset start time
|
||||||
|
start_time = time.monotonic()
|
||||||
|
_LOGGER.info("HomeAssistant record migration done")
|
||||||
|
|
||||||
# Check if Container is is_running
|
# 4
|
||||||
if not await self.instance.is_running():
|
# Timeout
|
||||||
_LOGGER.error("Home Assistant is crashed!")
|
if time.monotonic() - start_time > self.wait_boot:
|
||||||
|
_LOGGER.warning("Don't wait anymore of HomeAssistant startup!")
|
||||||
break
|
break
|
||||||
|
|
||||||
_LOGGER.warning("Don't wait anymore of HomeAssistant startup!")
|
|
||||||
self._error_state = True
|
self._error_state = True
|
||||||
raise HomeAssistantError()
|
raise HomeAssistantError()
|
||||||
|
@@ -293,6 +293,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
# Stop Home-Assistant if they will be restored later
|
# Stop Home-Assistant if they will be restored later
|
||||||
if homeassistant and FOLDER_HOMEASSISTANT in folders:
|
if homeassistant and FOLDER_HOMEASSISTANT in folders:
|
||||||
await self.sys_homeassistant.stop()
|
await self.sys_homeassistant.stop()
|
||||||
|
snapshot.restore_homeassistant()
|
||||||
|
|
||||||
# Process folders
|
# Process folders
|
||||||
if folders:
|
if folders:
|
||||||
@@ -304,7 +305,6 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
if homeassistant:
|
if homeassistant:
|
||||||
_LOGGER.info("Restore %s run Home-Assistant",
|
_LOGGER.info("Restore %s run Home-Assistant",
|
||||||
snapshot.slug)
|
snapshot.slug)
|
||||||
snapshot.restore_homeassistant()
|
|
||||||
task_hass = self.sys_create_task(
|
task_hass = self.sys_create_task(
|
||||||
self.sys_homeassistant.update(
|
self.sys_homeassistant.update(
|
||||||
snapshot.homeassistant_version))
|
snapshot.homeassistant_version))
|
||||||
@@ -322,12 +322,20 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
_LOGGER.info("Restore %s old add-ons", snapshot.slug)
|
_LOGGER.info("Restore %s old add-ons", snapshot.slug)
|
||||||
await snapshot.restore_addons(addon_list)
|
await snapshot.restore_addons(addon_list)
|
||||||
|
|
||||||
# make sure homeassistant run agen
|
# Make sure homeassistant run agen
|
||||||
if task_hass:
|
if task_hass:
|
||||||
_LOGGER.info("Restore %s wait for Home-Assistant",
|
_LOGGER.info("Restore %s wait for Home-Assistant",
|
||||||
snapshot.slug)
|
snapshot.slug)
|
||||||
await task_hass
|
await task_hass
|
||||||
await self.sys_homeassistant.start()
|
|
||||||
|
# Do we need start HomeAssistant?
|
||||||
|
if not await self.sys_homeassistant.is_running():
|
||||||
|
await self.sys_homeassistant.start()
|
||||||
|
|
||||||
|
# Check If we can access to API / otherwise restart
|
||||||
|
if not await self.sys_homeassistant.check_api_state():
|
||||||
|
_LOGGER.warning("Need restart HomeAssistant for API")
|
||||||
|
await self.sys_homeassistant.restart()
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Restore %s error", snapshot.slug)
|
_LOGGER.exception("Restore %s error", snapshot.slug)
|
||||||
|
@@ -20,7 +20,7 @@ from ..const import (
|
|||||||
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_IMAGE,
|
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_IMAGE,
|
||||||
ATTR_PORT, ATTR_SSL, ATTR_PASSWORD, ATTR_WATCHDOG, ATTR_BOOT, ATTR_CRYPTO,
|
ATTR_PORT, ATTR_SSL, ATTR_PASSWORD, ATTR_WATCHDOG, ATTR_BOOT, ATTR_CRYPTO,
|
||||||
ATTR_LAST_VERSION, ATTR_PROTECTED, ATTR_WAIT_BOOT, ATTR_SIZE,
|
ATTR_LAST_VERSION, ATTR_PROTECTED, ATTR_WAIT_BOOT, ATTR_SIZE,
|
||||||
CRYPTO_AES128)
|
ATTR_REFRESH_TOKEN, CRYPTO_AES128)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..utils.json import write_json_file
|
from ..utils.json import write_json_file
|
||||||
from ..utils.tar import SecureTarFile
|
from ..utils.tar import SecureTarFile
|
||||||
@@ -387,6 +387,8 @@ class Snapshot(CoreSysAttributes):
|
|||||||
# API/Proxy
|
# API/Proxy
|
||||||
self.homeassistant[ATTR_PORT] = self.sys_homeassistant.api_port
|
self.homeassistant[ATTR_PORT] = self.sys_homeassistant.api_port
|
||||||
self.homeassistant[ATTR_SSL] = self.sys_homeassistant.api_ssl
|
self.homeassistant[ATTR_SSL] = self.sys_homeassistant.api_ssl
|
||||||
|
self.homeassistant[ATTR_REFRESH_TOKEN] = \
|
||||||
|
self._encrypt_data(self.sys_homeassistant.refresh_token)
|
||||||
self.homeassistant[ATTR_PASSWORD] = \
|
self.homeassistant[ATTR_PASSWORD] = \
|
||||||
self._encrypt_data(self.sys_homeassistant.api_password)
|
self._encrypt_data(self.sys_homeassistant.api_password)
|
||||||
|
|
||||||
@@ -405,6 +407,8 @@ class Snapshot(CoreSysAttributes):
|
|||||||
# API/Proxy
|
# API/Proxy
|
||||||
self.sys_homeassistant.api_port = self.homeassistant[ATTR_PORT]
|
self.sys_homeassistant.api_port = self.homeassistant[ATTR_PORT]
|
||||||
self.sys_homeassistant.api_ssl = self.homeassistant[ATTR_SSL]
|
self.sys_homeassistant.api_ssl = self.homeassistant[ATTR_SSL]
|
||||||
|
self.sys_homeassistant.refresh_token = \
|
||||||
|
self._decrypt_data(self.homeassistant[ATTR_REFRESH_TOKEN])
|
||||||
self.sys_homeassistant.api_password = \
|
self.sys_homeassistant.api_password = \
|
||||||
self._decrypt_data(self.homeassistant[ATTR_PASSWORD])
|
self._decrypt_data(self.homeassistant[ATTR_PASSWORD])
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ from ..const import (
|
|||||||
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_IMAGE,
|
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_IMAGE,
|
||||||
ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG, ATTR_BOOT, ATTR_SIZE,
|
ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG, ATTR_BOOT, ATTR_SIZE,
|
||||||
ATTR_LAST_VERSION, ATTR_WAIT_BOOT, ATTR_PROTECTED, ATTR_CRYPTO,
|
ATTR_LAST_VERSION, ATTR_WAIT_BOOT, ATTR_PROTECTED, ATTR_CRYPTO,
|
||||||
|
ATTR_REFRESH_TOKEN,
|
||||||
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
||||||
SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CRYPTO_AES128)
|
SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CRYPTO_AES128)
|
||||||
from ..validate import NETWORK_PORT, REPOSITORIES, DOCKER_IMAGE
|
from ..validate import NETWORK_PORT, REPOSITORIES, DOCKER_IMAGE
|
||||||
@@ -40,6 +41,7 @@ SCHEMA_SNAPSHOT = vol.Schema({
|
|||||||
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=600):
|
vol.Optional(ATTR_WAIT_BOOT, default=600):
|
||||||
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
||||||
|
@@ -9,7 +9,6 @@ UTC = pytz.utc
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
FREEGEOIP_URL = "https://freegeoip.net/json/"
|
|
||||||
|
|
||||||
# Copyright (c) Django Software Foundation and individual contributors.
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
attr==0.3.1
|
attr==0.3.1
|
||||||
async_timeout==3.0.0
|
async_timeout==3.0.0
|
||||||
aiohttp==3.3.2
|
aiohttp==3.4.0
|
||||||
docker==3.4.0
|
docker==3.5.0
|
||||||
colorlog==3.1.2
|
colorlog==3.1.2
|
||||||
voluptuous==0.11.1
|
voluptuous==0.11.5
|
||||||
gitpython==2.1.10
|
gitpython==2.1.10
|
||||||
pytz==2018.4
|
pytz==2018.4
|
||||||
pyudev==0.21.0
|
pyudev==0.21.0
|
||||||
pycryptodome==3.6.4
|
pycryptodome==3.6.6
|
||||||
cpe==1.2.1
|
cpe==1.2.1
|
||||||
uvloop==0.11.1
|
uvloop==0.11.2
|
||||||
cchardet==2.1.1
|
cchardet==2.1.1
|
||||||
|
Reference in New Issue
Block a user