mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-27 02:56:31 +00:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
commit
24db0fdb86
15
API.md
15
API.md
@ -350,15 +350,8 @@ Get all available addons.
|
|||||||
"installed": "none|INSTALL_VERSION",
|
"installed": "none|INSTALL_VERSION",
|
||||||
"detached": "bool",
|
"detached": "bool",
|
||||||
"build": "bool",
|
"build": "bool",
|
||||||
"privileged": ["NET_ADMIN", "SYS_ADMIN"],
|
|
||||||
"devices": ["/dev/xy"],
|
|
||||||
"url": "null|url",
|
"url": "null|url",
|
||||||
"logo": "bool",
|
"logo": "bool"
|
||||||
"audio": "bool",
|
|
||||||
"gpio": "bool",
|
|
||||||
"stdin": "bool",
|
|
||||||
"hassio_api": "bool",
|
|
||||||
"homeassistant_api": "bool"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repositories": [
|
"repositories": [
|
||||||
@ -392,9 +385,13 @@ Get all available addons.
|
|||||||
"options": "{}",
|
"options": "{}",
|
||||||
"network": "{}|null",
|
"network": "{}|null",
|
||||||
"host_network": "bool",
|
"host_network": "bool",
|
||||||
|
"host_ipc": "bool",
|
||||||
|
"host_dbus": "bool",
|
||||||
"privileged": ["NET_ADMIN", "SYS_ADMIN"],
|
"privileged": ["NET_ADMIN", "SYS_ADMIN"],
|
||||||
"devices": ["/dev/xy"],
|
"devices": ["/dev/xy"],
|
||||||
|
"auto_uart": "bool",
|
||||||
"logo": "bool",
|
"logo": "bool",
|
||||||
|
"changelog": "bool",
|
||||||
"hassio_api": "bool",
|
"hassio_api": "bool",
|
||||||
"homeassistant_api": "bool",
|
"homeassistant_api": "bool",
|
||||||
"stdin": "bool",
|
"stdin": "bool",
|
||||||
@ -408,6 +405,8 @@ Get all available addons.
|
|||||||
|
|
||||||
- GET `/addons/{addon}/logo`
|
- GET `/addons/{addon}/logo`
|
||||||
|
|
||||||
|
- GET `/addons/{addon}/changelog`
|
||||||
|
|
||||||
- POST `/addons/{addon}/options`
|
- POST `/addons/{addon}/options`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
28
Dockerfile
28
Dockerfile
@ -1,21 +1,25 @@
|
|||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
# add env
|
# Add env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
# setup base
|
# Setup base
|
||||||
RUN apk add --no-cache python3 python3-dev \
|
RUN apk add --no-cache \
|
||||||
libressl libressl-dev \
|
python3 \
|
||||||
libffi libffi-dev \
|
git \
|
||||||
musl musl-dev \
|
socat \
|
||||||
gcc libstdc++ \
|
libstdc++ \
|
||||||
git socat \
|
&& apk add --no-cache --virtual .build-dependencies \
|
||||||
&& pip3 install --no-cache-dir --upgrade pip \
|
make \
|
||||||
&& pip3 install --no-cache-dir --upgrade cryptography jwcrypto \
|
python3-dev \
|
||||||
&& apk del python3-dev libressl-dev libffi-dev musl-dev gcc
|
g++ \
|
||||||
|
&& pip3 install --no-cache-dir \
|
||||||
|
uvloop \
|
||||||
|
cchardet \
|
||||||
|
&& apk del .build-dependencies
|
||||||
|
|
||||||
# install HassIO
|
# Install HassIO
|
||||||
COPY . /usr/src/hassio
|
COPY . /usr/src/hassio
|
||||||
RUN pip3 install --no-cache-dir /usr/src/hassio \
|
RUN pip3 install --no-cache-dir /usr/src/hassio \
|
||||||
&& rm -rf /usr/src/hassio
|
&& rm -rf /usr/src/hassio
|
||||||
|
@ -10,9 +10,19 @@ import hassio.core as core
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_use_uvloop():
|
||||||
|
"""Attempt to use uvloop."""
|
||||||
|
try:
|
||||||
|
import uvloop
|
||||||
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
bootstrap.initialize_logging()
|
bootstrap.initialize_logging()
|
||||||
|
attempt_use_uvloop()
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
if not bootstrap.check_environment():
|
if not bootstrap.check_environment():
|
||||||
|
@ -21,7 +21,8 @@ from ..const import (
|
|||||||
STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM,
|
STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM,
|
||||||
ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI,
|
ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI,
|
||||||
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_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
||||||
|
ATTR_HOST_DBUS, ATTR_AUTO_UART)
|
||||||
from .util import check_installed
|
from .util import check_installed
|
||||||
from ..dock.addon import DockerAddon
|
from ..dock.addon import DockerAddon
|
||||||
from ..tools import write_json_file, read_json_file
|
from ..tools import write_json_file, read_json_file
|
||||||
@ -243,11 +244,26 @@ class Addon(object):
|
|||||||
"""Return True if addon run on host network."""
|
"""Return True if addon run on host network."""
|
||||||
return self._mesh[ATTR_HOST_NETWORK]
|
return self._mesh[ATTR_HOST_NETWORK]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host_ipc(self):
|
||||||
|
"""Return True if addon run on host IPC namespace."""
|
||||||
|
return self._mesh[ATTR_HOST_IPC]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host_dbus(self):
|
||||||
|
"""Return True if addon run on host DBUS."""
|
||||||
|
return self._mesh[ATTR_HOST_DBUS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def devices(self):
|
||||||
"""Return devices of addon."""
|
"""Return devices of addon."""
|
||||||
return self._mesh.get(ATTR_DEVICES)
|
return self._mesh.get(ATTR_DEVICES)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_uart(self):
|
||||||
|
"""Return True if we should map all uart device."""
|
||||||
|
return self._mesh.get(ATTR_AUTO_UART)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tmpfs(self):
|
def tmpfs(self):
|
||||||
"""Return tmpfs of addon."""
|
"""Return tmpfs of addon."""
|
||||||
@ -343,6 +359,11 @@ class Addon(object):
|
|||||||
"""Return True if a logo exists."""
|
"""Return True if a logo exists."""
|
||||||
return self.path_logo.exists()
|
return self.path_logo.exists()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_changelog(self):
|
||||||
|
"""Return True if a changelog exists."""
|
||||||
|
return self.path_changelog.exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_arch(self):
|
def supported_arch(self):
|
||||||
"""Return list of supported arch."""
|
"""Return list of supported arch."""
|
||||||
@ -402,6 +423,11 @@ class Addon(object):
|
|||||||
"""Return path to addon logo."""
|
"""Return path to addon logo."""
|
||||||
return Path(self.path_location, 'logo.png')
|
return Path(self.path_location, 'logo.png')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_changelog(self):
|
||||||
|
"""Return path to addon changelog."""
|
||||||
|
return Path(self.path_location, 'CHANGELOG.md')
|
||||||
|
|
||||||
def write_options(self):
|
def write_options(self):
|
||||||
"""Return True if addon options is written to data."""
|
"""Return True if addon options is written to data."""
|
||||||
schema = self.schema
|
schema = self.schema
|
||||||
|
@ -14,9 +14,10 @@ from ..const import (
|
|||||||
ARCH_AARCH64, ARCH_AMD64, ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED,
|
ARCH_AARCH64, ARCH_AMD64, ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED,
|
||||||
ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED,
|
ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED,
|
||||||
ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK, ATTR_UUID,
|
ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK, ATTR_UUID,
|
||||||
ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT,
|
ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_HOST_IPC,
|
||||||
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)
|
||||||
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL
|
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -92,7 +93,10 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_WEBUI):
|
vol.Optional(ATTR_WEBUI):
|
||||||
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
|
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
|
||||||
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_HOST_DBUS, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
|
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
|
||||||
|
vol.Optional(ATTR_AUTO_UART, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_TMPFS):
|
vol.Optional(ATTR_TMPFS):
|
||||||
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
||||||
vol.Optional(ATTR_MAP, default=[]): [vol.Match(RE_VOLUME)],
|
vol.Optional(ATTR_MAP, default=[]): [vol.Match(RE_VOLUME)],
|
||||||
|
@ -104,6 +104,8 @@ class RestAPI(object):
|
|||||||
'/addons/{addon}/rebuild', api_addons.rebuild)
|
'/addons/{addon}/rebuild', api_addons.rebuild)
|
||||||
self.webapp.router.add_get('/addons/{addon}/logs', api_addons.logs)
|
self.webapp.router.add_get('/addons/{addon}/logs', api_addons.logs)
|
||||||
self.webapp.router.add_get('/addons/{addon}/logo', api_addons.logo)
|
self.webapp.router.add_get('/addons/{addon}/logo', api_addons.logo)
|
||||||
|
self.webapp.router.add_get(
|
||||||
|
'/addons/{addon}/changelog', api_addons.changelog)
|
||||||
self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin)
|
self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin)
|
||||||
|
|
||||||
def register_security(self):
|
def register_security(self):
|
||||||
|
@ -14,7 +14,8 @@ from ..const import (
|
|||||||
ATTR_INSTALLED, ATTR_LOGO, ATTR_WEBUI, ATTR_DEVICES, ATTR_PRIVILEGED,
|
ATTR_INSTALLED, ATTR_LOGO, ATTR_WEBUI, ATTR_DEVICES, ATTR_PRIVILEGED,
|
||||||
ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API,
|
ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API,
|
||||||
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, BOOT_AUTO, BOOT_MANUAL,
|
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, BOOT_AUTO, BOOT_MANUAL,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY)
|
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS,
|
||||||
|
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT)
|
||||||
from ..validate import DOCKER_PORTS
|
from ..validate import DOCKER_PORTS
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -74,15 +75,8 @@ class APIAddons(object):
|
|||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_PRIVILEGED: addon.privileged,
|
|
||||||
ATTR_DEVICES: self._pretty_devices(addon),
|
|
||||||
ATTR_URL: addon.url,
|
ATTR_URL: addon.url,
|
||||||
ATTR_LOGO: addon.with_logo,
|
ATTR_LOGO: addon.with_logo,
|
||||||
ATTR_STDIN: addon.with_stdin,
|
|
||||||
ATTR_HASSIO_API: addon.access_hassio_api,
|
|
||||||
ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api,
|
|
||||||
ATTR_AUDIO: addon.with_audio,
|
|
||||||
ATTR_GPIO: addon.with_gpio,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
data_repositories = []
|
data_repositories = []
|
||||||
@ -126,9 +120,12 @@ class APIAddons(object):
|
|||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_NETWORK: addon.ports,
|
ATTR_NETWORK: addon.ports,
|
||||||
ATTR_HOST_NETWORK: addon.host_network,
|
ATTR_HOST_NETWORK: addon.host_network,
|
||||||
|
ATTR_HOST_IPC: addon.host_ipc,
|
||||||
|
ATTR_HOST_DBUS: addon.host_dbus,
|
||||||
ATTR_PRIVILEGED: addon.privileged,
|
ATTR_PRIVILEGED: addon.privileged,
|
||||||
ATTR_DEVICES: self._pretty_devices(addon),
|
ATTR_DEVICES: self._pretty_devices(addon),
|
||||||
ATTR_LOGO: addon.with_logo,
|
ATTR_LOGO: addon.with_logo,
|
||||||
|
ATTR_CHANGELOG: addon.with_changelog,
|
||||||
ATTR_WEBUI: addon.webui,
|
ATTR_WEBUI: addon.webui,
|
||||||
ATTR_STDIN: addon.with_stdin,
|
ATTR_STDIN: addon.with_stdin,
|
||||||
ATTR_HASSIO_API: addon.access_hassio_api,
|
ATTR_HASSIO_API: addon.access_hassio_api,
|
||||||
@ -238,6 +235,16 @@ class APIAddons(object):
|
|||||||
with addon.path_logo.open('rb') as png:
|
with addon.path_logo.open('rb') as png:
|
||||||
return png.read()
|
return png.read()
|
||||||
|
|
||||||
|
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||||
|
async def changelog(self, request):
|
||||||
|
"""Return changelog from addon."""
|
||||||
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
|
if not addon.with_changelog:
|
||||||
|
raise RuntimeError("No changelog found!")
|
||||||
|
|
||||||
|
with addon.path_changelog.open('r') as changelog:
|
||||||
|
return changelog.read()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stdin(self, request):
|
async def stdin(self, request):
|
||||||
"""Write to stdin of addon."""
|
"""Write to stdin of addon."""
|
||||||
|
@ -17,10 +17,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def json_loads(data):
|
def json_loads(data):
|
||||||
"""Extract json from string with support for '' and None."""
|
"""Extract json from string with support for '' and None."""
|
||||||
|
if not data:
|
||||||
|
return {}
|
||||||
try:
|
try:
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return {}
|
raise RuntimeError("Invalid json")
|
||||||
|
|
||||||
|
|
||||||
def api_process(method):
|
def api_process(method):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '0.75'
|
HASSIO_VERSION = '0.76'
|
||||||
|
|
||||||
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
||||||
'hassio/{}/version.json')
|
'hassio/{}/version.json')
|
||||||
@ -50,9 +50,11 @@ RESULT_OK = 'ok'
|
|||||||
CONTENT_TYPE_BINARY = 'application/octet-stream'
|
CONTENT_TYPE_BINARY = 'application/octet-stream'
|
||||||
CONTENT_TYPE_PNG = 'image/png'
|
CONTENT_TYPE_PNG = 'image/png'
|
||||||
CONTENT_TYPE_JSON = 'application/json'
|
CONTENT_TYPE_JSON = 'application/json'
|
||||||
|
CONTENT_TYPE_TEXT = 'text/plain'
|
||||||
HEADER_HA_ACCESS = 'x-ha-access'
|
HEADER_HA_ACCESS = 'x-ha-access'
|
||||||
|
|
||||||
ATTR_WATCHDOG = 'watchdog'
|
ATTR_WATCHDOG = 'watchdog'
|
||||||
|
ATTR_CHANGELOG = 'changelog'
|
||||||
ATTR_DATE = 'date'
|
ATTR_DATE = 'date'
|
||||||
ATTR_ARCH = 'arch'
|
ATTR_ARCH = 'arch'
|
||||||
ATTR_HOSTNAME = 'hostname'
|
ATTR_HOSTNAME = 'hostname'
|
||||||
@ -64,6 +66,7 @@ ATTR_SOURCE = 'source'
|
|||||||
ATTR_FEATURES = 'features'
|
ATTR_FEATURES = 'features'
|
||||||
ATTR_ADDONS = 'addons'
|
ATTR_ADDONS = 'addons'
|
||||||
ATTR_VERSION = 'version'
|
ATTR_VERSION = 'version'
|
||||||
|
ATTR_AUTO_UART = 'auto_uart'
|
||||||
ATTR_LAST_BOOT = 'last_boot'
|
ATTR_LAST_BOOT = 'last_boot'
|
||||||
ATTR_LAST_VERSION = 'last_version'
|
ATTR_LAST_VERSION = 'last_version'
|
||||||
ATTR_BETA_CHANNEL = 'beta_channel'
|
ATTR_BETA_CHANNEL = 'beta_channel'
|
||||||
@ -100,6 +103,8 @@ ATTR_BUILD = 'build'
|
|||||||
ATTR_DEVICES = 'devices'
|
ATTR_DEVICES = 'devices'
|
||||||
ATTR_ENVIRONMENT = 'environment'
|
ATTR_ENVIRONMENT = 'environment'
|
||||||
ATTR_HOST_NETWORK = 'host_network'
|
ATTR_HOST_NETWORK = 'host_network'
|
||||||
|
ATTR_HOST_IPC = 'host_ipc'
|
||||||
|
ATTR_HOST_DBUS = 'host_dbus'
|
||||||
ATTR_NETWORK = 'network'
|
ATTR_NETWORK = 'network'
|
||||||
ATTR_TMPFS = 'tmpfs'
|
ATTR_TMPFS = 'tmpfs'
|
||||||
ATTR_PRIVILEGED = 'privileged'
|
ATTR_PRIVILEGED = 'privileged'
|
||||||
|
@ -42,8 +42,8 @@ class HassIO(object):
|
|||||||
self.scheduler = Scheduler(loop)
|
self.scheduler = Scheduler(loop)
|
||||||
self.api = RestAPI(config, loop)
|
self.api = RestAPI(config, loop)
|
||||||
self.hardware = Hardware()
|
self.hardware = Hardware()
|
||||||
self.docker = DockerAPI()
|
self.docker = DockerAPI(self.hardware)
|
||||||
self.dns = DNSForward()
|
self.dns = DNSForward(loop)
|
||||||
|
|
||||||
# init basic docker container
|
# init basic docker container
|
||||||
self.supervisor = DockerSupervisor(
|
self.supervisor = DockerSupervisor(
|
||||||
|
@ -11,8 +11,9 @@ COMMAND = "socat UDP-RECVFROM:53,fork UDP-SENDTO:127.0.0.11:53"
|
|||||||
class DNSForward(object):
|
class DNSForward(object):
|
||||||
"""Manage DNS forwarding to internal DNS."""
|
"""Manage DNS forwarding to internal DNS."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, loop):
|
||||||
"""Initialize DNS forwarding."""
|
"""Initialize DNS forwarding."""
|
||||||
|
self.loop = loop
|
||||||
self.proc = None
|
self.proc = None
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
@ -23,6 +24,7 @@ class DNSForward(object):
|
|||||||
stdin=asyncio.subprocess.DEVNULL,
|
stdin=asyncio.subprocess.DEVNULL,
|
||||||
stdout=asyncio.subprocess.DEVNULL,
|
stdout=asyncio.subprocess.DEVNULL,
|
||||||
stderr=asyncio.subprocess.DEVNULL,
|
stderr=asyncio.subprocess.DEVNULL,
|
||||||
|
loop=self.loop
|
||||||
)
|
)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Can't start DNS forwarding -> %s", err)
|
_LOGGER.error("Can't start DNS forwarding -> %s", err)
|
||||||
|
@ -16,11 +16,12 @@ class DockerAPI(object):
|
|||||||
This class is not AsyncIO safe!
|
This class is not AsyncIO safe!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, hardware):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize docker base wrapper."""
|
||||||
self.docker = docker.DockerClient(
|
self.docker = docker.DockerClient(
|
||||||
base_url="unix:/{}".format(str(SOCKET_DOCKER)), version='auto')
|
base_url="unix:/{}".format(str(SOCKET_DOCKER)), version='auto')
|
||||||
self.network = DockerNetwork(self.docker)
|
self.network = DockerNetwork(self.docker)
|
||||||
|
self.hardware = hardware
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def images(self):
|
def images(self):
|
||||||
|
@ -45,6 +45,12 @@ class DockerAddon(DockerInterface):
|
|||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return "addon_{}".format(self.addon.slug)
|
return "addon_{}".format(self.addon.slug)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ipc(self):
|
||||||
|
"""Return the IPC namespace."""
|
||||||
|
if self.addon.host_ipc:
|
||||||
|
return 'host'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self):
|
def hostname(self):
|
||||||
"""Return slug/id of addon."""
|
"""Return slug/id of addon."""
|
||||||
@ -74,14 +80,17 @@ class DockerAddon(DockerInterface):
|
|||||||
"""Return needed devices."""
|
"""Return needed devices."""
|
||||||
devices = self.addon.devices or []
|
devices = self.addon.devices or []
|
||||||
|
|
||||||
# use audio devices
|
# Use audio devices
|
||||||
if self.addon.with_audio and AUDIO_DEVICE not in devices:
|
if self.addon.with_audio and AUDIO_DEVICE not in devices:
|
||||||
devices.append(AUDIO_DEVICE)
|
devices.append(AUDIO_DEVICE)
|
||||||
|
|
||||||
|
# Auto mapping UART devices
|
||||||
|
if self.addon.auto_uart:
|
||||||
|
for uart_dev in self.docker.hardware.serial_devices:
|
||||||
|
devices.append("{0}:{0}:rwm".format(uart_dev))
|
||||||
|
|
||||||
# Return None if no devices is present
|
# Return None if no devices is present
|
||||||
if devices:
|
return devices or None
|
||||||
return devices
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
@ -95,6 +104,17 @@ class DockerAddon(DockerInterface):
|
|||||||
if host_port
|
if host_port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def security_opt(self):
|
||||||
|
"""Controlling security opt."""
|
||||||
|
privileged = self.addon.privileged or []
|
||||||
|
|
||||||
|
# Disable AppArmor sinse it make troubles wit SYS_ADMIN
|
||||||
|
if 'SYS_ADMIN' in privileged:
|
||||||
|
return [
|
||||||
|
"apparmor:unconfined",
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tmpfs(self):
|
def tmpfs(self):
|
||||||
"""Return tmpfs for docker add-on."""
|
"""Return tmpfs for docker add-on."""
|
||||||
@ -123,7 +143,7 @@ class DockerAddon(DockerInterface):
|
|||||||
"""Generate volumes for mappings."""
|
"""Generate volumes for mappings."""
|
||||||
volumes = {
|
volumes = {
|
||||||
str(self.addon.path_extern_data): {
|
str(self.addon.path_extern_data): {
|
||||||
'bind': '/data', 'mode': 'rw'
|
'bind': "/data", 'mode': 'rw'
|
||||||
}}
|
}}
|
||||||
|
|
||||||
addon_mapping = self.addon.map_volumes
|
addon_mapping = self.addon.map_volumes
|
||||||
@ -132,44 +152,51 @@ class DockerAddon(DockerInterface):
|
|||||||
if MAP_CONFIG in addon_mapping:
|
if MAP_CONFIG in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.config.path_extern_config): {
|
str(self.config.path_extern_config): {
|
||||||
'bind': '/config', 'mode': addon_mapping[MAP_CONFIG]
|
'bind': "/config", 'mode': addon_mapping[MAP_CONFIG]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
if MAP_SSL in addon_mapping:
|
if MAP_SSL in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.config.path_extern_ssl): {
|
str(self.config.path_extern_ssl): {
|
||||||
'bind': '/ssl', 'mode': addon_mapping[MAP_SSL]
|
'bind': "/ssl", 'mode': addon_mapping[MAP_SSL]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
if MAP_ADDONS in addon_mapping:
|
if MAP_ADDONS in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.config.path_extern_addons_local): {
|
str(self.config.path_extern_addons_local): {
|
||||||
'bind': '/addons', 'mode': addon_mapping[MAP_ADDONS]
|
'bind': "/addons", 'mode': addon_mapping[MAP_ADDONS]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
if MAP_BACKUP in addon_mapping:
|
if MAP_BACKUP in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.config.path_extern_backup): {
|
str(self.config.path_extern_backup): {
|
||||||
'bind': '/backup', 'mode': addon_mapping[MAP_BACKUP]
|
'bind': "/backup", 'mode': addon_mapping[MAP_BACKUP]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
if MAP_SHARE in addon_mapping:
|
if MAP_SHARE in addon_mapping:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
str(self.config.path_extern_share): {
|
str(self.config.path_extern_share): {
|
||||||
'bind': '/share', 'mode': addon_mapping[MAP_SHARE]
|
'bind': "/share", 'mode': addon_mapping[MAP_SHARE]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
# init other hardware mappings
|
# init other hardware mappings
|
||||||
if self.addon.with_gpio:
|
if self.addon.with_gpio:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
'/sys/class/gpio': {
|
"/sys/class/gpio": {
|
||||||
'bind': '/sys/class/gpio', 'mode': "rw"
|
'bind': "/sys/class/gpio", 'mode': 'rw'
|
||||||
},
|
},
|
||||||
'/sys/devices/platform/soc': {
|
"/sys/devices/platform/soc": {
|
||||||
'bind': '/sys/devices/platform/soc', 'mode': "rw"
|
'bind': "/sys/devices/platform/soc", 'mode': 'rw'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# host dbus system
|
||||||
|
if self.addon.host_dbus:
|
||||||
|
volumes.update({
|
||||||
|
"/var/run/dbus": {
|
||||||
|
'bind': "/var/run/dbus", 'mode': 'rw'
|
||||||
|
}})
|
||||||
|
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
@ -193,12 +220,14 @@ class DockerAddon(DockerInterface):
|
|||||||
hostname=self.hostname,
|
hostname=self.hostname,
|
||||||
detach=True,
|
detach=True,
|
||||||
init=True,
|
init=True,
|
||||||
|
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,
|
||||||
ports=self.ports,
|
ports=self.ports,
|
||||||
extra_hosts=self.network_mapping,
|
extra_hosts=self.network_mapping,
|
||||||
devices=self.devices,
|
devices=self.devices,
|
||||||
cap_add=self.addon.privileged,
|
cap_add=self.addon.privileged,
|
||||||
|
security_opt=self.security_opt,
|
||||||
environment=self.environment,
|
environment=self.environment,
|
||||||
volumes=self.volumes,
|
volumes=self.volumes,
|
||||||
tmpfs=self.tmpfs
|
tmpfs=self.tmpfs
|
||||||
|
@ -20,6 +20,7 @@ PROC_STAT = Path("/proc/stat")
|
|||||||
RE_BOOT_TIME = re.compile(r"btime (\d+)")
|
RE_BOOT_TIME = re.compile(r"btime (\d+)")
|
||||||
|
|
||||||
GPIO_DEVICES = Path("/sys/class/gpio")
|
GPIO_DEVICES = Path("/sys/class/gpio")
|
||||||
|
RE_TTY = re.compile(r"tty[A-Z]+")
|
||||||
|
|
||||||
|
|
||||||
class Hardware(object):
|
class Hardware(object):
|
||||||
@ -34,7 +35,7 @@ class Hardware(object):
|
|||||||
"""Return all serial and connected devices."""
|
"""Return all serial and connected devices."""
|
||||||
dev_list = set()
|
dev_list = set()
|
||||||
for device in self.context.list_devices(subsystem='tty'):
|
for device in self.context.list_devices(subsystem='tty'):
|
||||||
if 'ID_VENDOR' in device:
|
if 'ID_VENDOR' in device or RE_TTY.search(device.device_node):
|
||||||
dev_list.add(device.device_node)
|
dev_list.add(device.device_node)
|
||||||
|
|
||||||
return dev_list
|
return dev_list
|
||||||
|
2
setup.py
2
setup.py
@ -24,7 +24,7 @@ setup(
|
|||||||
'Topic :: Scientific/Engineering :: Atmospheric Science',
|
'Topic :: Scientific/Engineering :: Atmospheric Science',
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.6',
|
||||||
],
|
],
|
||||||
keywords=['docker', 'home-assistant', 'api'],
|
keywords=['docker', 'home-assistant', 'api'],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user