mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-13 06:59:31 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
754cd64213 | ||
![]() |
113b62ee77 | ||
![]() |
d9874c4c3e | ||
![]() |
ca44e858c5 | ||
![]() |
c7ca4de307 | ||
![]() |
b77146a4e0 | ||
![]() |
45b4800378 | ||
![]() |
7f9232d2b9 | ||
![]() |
d90426f745 | ||
![]() |
c2deabb672 | ||
![]() |
ead5993f3e | ||
![]() |
1bcd74e8fa | ||
![]() |
118da3c275 | ||
![]() |
d7bb9013d4 | ||
![]() |
812c46d82b | ||
![]() |
c0462b28cd | ||
![]() |
82b2f66920 | ||
![]() |
01da42e1b6 | ||
![]() |
d652d22547 | ||
![]() |
baea84abe6 | ||
![]() |
c2d705a42a | ||
![]() |
f10b433e1f | ||
![]() |
67f562a846 | ||
![]() |
1edec61133 | ||
![]() |
c13a33bf71 | ||
![]() |
2ae93ae7b1 | ||
![]() |
8451020afe | ||
![]() |
a48e568efc | ||
![]() |
dee2808cb5 | ||
![]() |
06a2ab26a2 | ||
![]() |
45de0f2f39 | ||
![]() |
bac5f704dc | ||
![]() |
79669a5d04 | ||
![]() |
a6e712c9ea | ||
![]() |
069fe99699 | ||
![]() |
4754f067ad | ||
![]() |
dce9818812 | ||
![]() |
d054b6dbb7 | ||
![]() |
3093165325 | ||
![]() |
fd9c5bd412 | ||
![]() |
9a8850fecd | ||
![]() |
b12175ab9a | ||
![]() |
b52f90187b | ||
![]() |
4eb02f474d | ||
![]() |
dfdcddfd0b | ||
![]() |
0391277bad | ||
![]() |
73643b9bfe | ||
![]() |
93a52b8382 | ||
![]() |
7a91bb1f6c | ||
![]() |
26efa998a1 | ||
![]() |
fc9f3fee0a | ||
![]() |
ec19bd570b | ||
![]() |
3335bad9e1 | ||
![]() |
71ae334e24 | ||
![]() |
0807651fbd | ||
![]() |
7026d42d77 | ||
![]() |
31047b9ec2 | ||
![]() |
714791de8f | ||
![]() |
c544fff2b2 | ||
![]() |
fc45670686 | ||
![]() |
5cefa0a2ee | ||
![]() |
a1910d4135 | ||
![]() |
f1fecdde3a | ||
![]() |
9ba4ea7d18 | ||
![]() |
58a455d639 | ||
![]() |
3ea85f6a28 | ||
![]() |
4e1469ada4 | ||
![]() |
5778f78f28 | ||
![]() |
227125cc0b | ||
![]() |
b36e178c45 | ||
![]() |
32c9198fb2 | ||
![]() |
6983dcc267 | ||
![]() |
813fcc41f0 | ||
![]() |
f4e9dd0f1c | ||
![]() |
7f074142bf | ||
![]() |
b6df37628d | ||
![]() |
7867eded50 | ||
![]() |
311abb8a90 | ||
![]() |
21303f4b05 | ||
![]() |
da3270af67 | ||
![]() |
35aae69f23 | ||
![]() |
118a2e1951 | ||
![]() |
9053341581 | ||
![]() |
27532a8a00 | ||
![]() |
7fdfa630b5 | ||
![]() |
3974d5859f | ||
![]() |
aa1c765c4b | ||
![]() |
e78385e7ea | ||
![]() |
9d59b56c94 | ||
![]() |
9d72dcabfc | ||
![]() |
a0b5d0b67e | ||
![]() |
2b5520405f | ||
![]() |
ca376b3fcd | ||
![]() |
11e3c0c547 | ||
![]() |
9da136e037 | ||
![]() |
9b3e59d876 | ||
![]() |
7a592795b5 | ||
![]() |
5b92137699 |
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -13,12 +13,6 @@
|
|||||||
- Or use this command: hass --version
|
- Or use this command: hass --version
|
||||||
-->
|
-->
|
||||||
|
|
||||||
**Home Assistant release with the issue:**
|
|
||||||
<!--
|
|
||||||
- Frontend -> Developer tools -> Info
|
|
||||||
- Or use this command: hass --version
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Operating environment (HassOS/Generic):**
|
**Operating environment (HassOS/Generic):**
|
||||||
<!--
|
<!--
|
||||||
Please provide details about your environment.
|
Please provide details about your environment.
|
||||||
|
13
.github/move.yml
vendored
Normal file
13
.github/move.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Configuration for move-issues - https://github.com/dessant/move-issues
|
||||||
|
|
||||||
|
# Delete the command comment. Ignored when the comment also contains other content
|
||||||
|
deleteCommand: true
|
||||||
|
# Close the source issue after moving
|
||||||
|
closeSourceIssue: true
|
||||||
|
# Lock the source issue after moving
|
||||||
|
lockSourceIssue: false
|
||||||
|
# Set custom aliases for targets
|
||||||
|
# aliases:
|
||||||
|
# r: repo
|
||||||
|
# or: owner/repo
|
||||||
|
|
@@ -1,6 +0,0 @@
|
|||||||
sudo: true
|
|
||||||
dist: xenial
|
|
||||||
install: pip install -U tox
|
|
||||||
language: python
|
|
||||||
python: 3.7
|
|
||||||
script: tox
|
|
45
API.md
45
API.md
@@ -22,7 +22,7 @@ On success / Code 200:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For access to API you need set the `X-HASSIO-KEY` they will be available for Add-ons/HomeAssistant with envoriment `HASSIO_TOKEN`.
|
For access to API you need set the `X-HASSIO-KEY` they will be available for Add-ons/HomeAssistant with environment `HASSIO_TOKEN`.
|
||||||
|
|
||||||
### Hass.io
|
### Hass.io
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ The addons from `addons` are only installed one.
|
|||||||
"arch": "armhf|aarch64|i386|amd64",
|
"arch": "armhf|aarch64|i386|amd64",
|
||||||
"channel": "stable|beta|dev",
|
"channel": "stable|beta|dev",
|
||||||
"timezone": "TIMEZONE",
|
"timezone": "TIMEZONE",
|
||||||
|
"ip_address": "ip address",
|
||||||
"wait_boot": "int",
|
"wait_boot": "int",
|
||||||
"addons": [
|
"addons": [
|
||||||
{
|
{
|
||||||
@@ -314,9 +315,10 @@ Load host configs from a USB stick.
|
|||||||
"CARD_ID": {
|
"CARD_ID": {
|
||||||
"name": "xy",
|
"name": "xy",
|
||||||
"type": "microphone",
|
"type": "microphone",
|
||||||
"devices": {
|
"devices": [
|
||||||
"DEV_ID": "type of device"
|
"chan_id": "channel ID",
|
||||||
}
|
"chan_type": "type of device"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,14 +347,16 @@ Load host configs from a USB stick.
|
|||||||
{
|
{
|
||||||
"version": "INSTALL_VERSION",
|
"version": "INSTALL_VERSION",
|
||||||
"last_version": "LAST_VERSION",
|
"last_version": "LAST_VERSION",
|
||||||
|
"arch": "arch",
|
||||||
"machine": "Image machine type",
|
"machine": "Image machine type",
|
||||||
|
"ip_address": "ip address",
|
||||||
"image": "str",
|
"image": "str",
|
||||||
"custom": "bool -> if custom image",
|
"custom": "bool -> if custom image",
|
||||||
"boot": "bool",
|
"boot": "bool",
|
||||||
"port": 8123,
|
"port": 8123,
|
||||||
"ssl": "bool",
|
"ssl": "bool",
|
||||||
"watchdog": "bool",
|
"watchdog": "bool",
|
||||||
"startup_time": 600
|
"wait_boot": 600
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -374,6 +378,7 @@ Output is the raw Docker log.
|
|||||||
- POST `/homeassistant/check`
|
- POST `/homeassistant/check`
|
||||||
- POST `/homeassistant/start`
|
- POST `/homeassistant/start`
|
||||||
- POST `/homeassistant/stop`
|
- POST `/homeassistant/stop`
|
||||||
|
- POST `/homeassistant/rebuild`
|
||||||
|
|
||||||
- POST `/homeassistant/options`
|
- POST `/homeassistant/options`
|
||||||
|
|
||||||
@@ -386,7 +391,7 @@ Output is the raw Docker log.
|
|||||||
"password": "",
|
"password": "",
|
||||||
"refresh_token": "",
|
"refresh_token": "",
|
||||||
"watchdog": "bool",
|
"watchdog": "bool",
|
||||||
"startup_time": 600
|
"wait_boot": 600
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -466,6 +471,7 @@ Get all available addons.
|
|||||||
"available": "bool",
|
"available": "bool",
|
||||||
"arch": ["armhf", "aarch64", "i386", "amd64"],
|
"arch": ["armhf", "aarch64", "i386", "amd64"],
|
||||||
"machine": "[raspberrypi2, tinker]",
|
"machine": "[raspberrypi2, tinker]",
|
||||||
|
"homeassistant": "null|min Home Assistant version",
|
||||||
"repository": "12345678|null",
|
"repository": "12345678|null",
|
||||||
"version": "null|VERSION_INSTALLED",
|
"version": "null|VERSION_INSTALLED",
|
||||||
"last_version": "LAST_VERSION",
|
"last_version": "LAST_VERSION",
|
||||||
@@ -474,6 +480,7 @@ Get all available addons.
|
|||||||
"build": "bool",
|
"build": "bool",
|
||||||
"options": "{}",
|
"options": "{}",
|
||||||
"network": "{}|null",
|
"network": "{}|null",
|
||||||
|
"network_description": "{}|null",
|
||||||
"host_network": "bool",
|
"host_network": "bool",
|
||||||
"host_pid": "bool",
|
"host_pid": "bool",
|
||||||
"host_ipc": "bool",
|
"host_ipc": "bool",
|
||||||
@@ -495,13 +502,19 @@ Get all available addons.
|
|||||||
"stdin": "bool",
|
"stdin": "bool",
|
||||||
"webui": "null|http(s)://[HOST]:port/xy/zx",
|
"webui": "null|http(s)://[HOST]:port/xy/zx",
|
||||||
"gpio": "bool",
|
"gpio": "bool",
|
||||||
|
"kernel_modules": "bool",
|
||||||
"devicetree": "bool",
|
"devicetree": "bool",
|
||||||
"docker_api": "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",
|
||||||
"services_role": "['service:access']",
|
"services_role": "['service:access']",
|
||||||
"discovery": "['service']"
|
"discovery": "['service']",
|
||||||
|
"ip_address": "ip address",
|
||||||
|
"ingress": "bool",
|
||||||
|
"ingress_entry": "null|/api/hassio_ingress/slug",
|
||||||
|
"ingress_url": "null|/api/hassio_ingress/slug/entry.html",
|
||||||
|
"ingress_port": "null|int"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -575,6 +588,23 @@ Write data to add-on stdin
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ingress
|
||||||
|
|
||||||
|
- POST `/ingress/session`
|
||||||
|
|
||||||
|
Create a new Session for access to ingress service.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session": "token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- VIEW `/ingress/{token}`
|
||||||
|
|
||||||
|
Ingress WebUI for this Add-on. The addon need support HASS Auth!
|
||||||
|
Need ingress session as cookie.
|
||||||
|
|
||||||
### discovery
|
### discovery
|
||||||
|
|
||||||
- GET `/discovery`
|
- GET `/discovery`
|
||||||
@@ -673,6 +703,7 @@ return:
|
|||||||
"hostname": "name",
|
"hostname": "name",
|
||||||
"machine": "type",
|
"machine": "type",
|
||||||
"arch": "arch",
|
"arch": "arch",
|
||||||
|
"supported_arch": ["arch1", "arch2"],
|
||||||
"channel": "stable|beta|dev"
|
"channel": "stable|beta|dev"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
10
Dockerfile
10
Dockerfile
@@ -3,6 +3,9 @@ FROM $BUILD_FROM
|
|||||||
|
|
||||||
# Install base
|
# Install base
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
|
openssl \
|
||||||
|
libffi \
|
||||||
|
musl \
|
||||||
git \
|
git \
|
||||||
socat \
|
socat \
|
||||||
glib \
|
glib \
|
||||||
@@ -12,8 +15,11 @@ RUN apk add --no-cache \
|
|||||||
# Install requirements
|
# Install requirements
|
||||||
COPY requirements.txt /usr/src/
|
COPY requirements.txt /usr/src/
|
||||||
RUN apk add --no-cache --virtual .build-dependencies \
|
RUN apk add --no-cache --virtual .build-dependencies \
|
||||||
make \
|
make \
|
||||||
g++ \
|
g++ \
|
||||||
|
openssl-dev \
|
||||||
|
libffi-dev \
|
||||||
|
musl-dev \
|
||||||
&& export MAKEFLAGS="-j$(nproc)" \
|
&& export MAKEFLAGS="-j$(nproc)" \
|
||||||
&& pip3 install --no-cache-dir -r /usr/src/requirements.txt \
|
&& pip3 install --no-cache-dir -r /usr/src/requirements.txt \
|
||||||
&& apk del .build-dependencies \
|
&& apk del .build-dependencies \
|
||||||
|
45
azure-pipelines.yml
Normal file
45
azure-pipelines.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Python package
|
||||||
|
# Create and test a Python package on multiple Python versions.
|
||||||
|
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
|
||||||
|
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
|
||||||
|
pr:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
- job: "Tox"
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-16.04'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UsePythonVersion@0
|
||||||
|
displayName: 'Use Python $(python.version)'
|
||||||
|
inputs:
|
||||||
|
versionSpec: '3.7'
|
||||||
|
|
||||||
|
- script: pip install tox
|
||||||
|
displayName: 'Install Tox'
|
||||||
|
|
||||||
|
- script: tox
|
||||||
|
displayName: 'Run Tox'
|
||||||
|
|
||||||
|
|
||||||
|
- job: "JQ"
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-16.04'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- script: sudo apt-get install -y jq
|
||||||
|
displayName: 'Install JQ'
|
||||||
|
|
||||||
|
- bash: |
|
||||||
|
shopt -s globstar
|
||||||
|
cat **/*.json | jq '.'
|
||||||
|
displayName: 'Run JQ'
|
@@ -9,21 +9,26 @@ from hassio import bootstrap
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def attempt_use_uvloop():
|
def initialize_event_loop():
|
||||||
"""Attempt to use uvloop."""
|
"""Attempt to use uvloop."""
|
||||||
try:
|
try:
|
||||||
import uvloop
|
import uvloop
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|
||||||
|
uvloop.install()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
return asyncio.get_event_loop()
|
||||||
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
# Init async event loop
|
||||||
|
loop = initialize_event_loop()
|
||||||
|
|
||||||
|
# Check if all information are available to setup Hass.io
|
||||||
if not bootstrap.check_environment():
|
if not bootstrap.check_environment():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -32,7 +37,7 @@ if __name__ == "__main__":
|
|||||||
loop.set_default_executor(executor)
|
loop.set_default_executor(executor)
|
||||||
|
|
||||||
_LOGGER.info("Initialize Hass.io setup")
|
_LOGGER.info("Initialize Hass.io setup")
|
||||||
coresys = bootstrap.initialize_coresys(loop)
|
coresys = loop.run_until_complete(bootstrap.initialize_coresys())
|
||||||
|
|
||||||
bootstrap.migrate_system_env(coresys)
|
bootstrap.migrate_system_env(coresys)
|
||||||
|
|
||||||
|
@@ -1,41 +1,106 @@
|
|||||||
"""Init file for Hass.io add-ons."""
|
"""Init file for Hass.io add-ons."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from ipaddress import IPv4Address, ip_address
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
import re
|
import re
|
||||||
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import tarfile
|
import tarfile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
from typing import Any, Awaitable, Dict, Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .validate import (
|
|
||||||
validate_options, SCHEMA_ADDON_SNAPSHOT, RE_VOLUME, RE_SERVICE,
|
|
||||||
MACHINE_ALL)
|
|
||||||
from .utils import check_installed, remove_data
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_BOOT, ATTR_MAP,
|
ATTR_ACCESS_TOKEN,
|
||||||
ATTR_OPTIONS, ATTR_PORTS, ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY,
|
ATTR_APPARMOR,
|
||||||
ATTR_URL, ATTR_ARCH, ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT,
|
ATTR_ARCH,
|
||||||
ATTR_HOST_NETWORK, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_STARTUP, ATTR_UUID,
|
ATTR_AUDIO,
|
||||||
STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM,
|
ATTR_AUDIO_INPUT,
|
||||||
ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI,
|
ATTR_AUDIO_OUTPUT,
|
||||||
ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
|
ATTR_AUTH_API,
|
||||||
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
ATTR_AUTO_UART,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
ATTR_AUTO_UPDATE,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
ATTR_BOOT,
|
||||||
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
ATTR_DESCRIPTON,
|
||||||
ATTR_MACHINE, ATTR_AUTH_API,
|
ATTR_DEVICES,
|
||||||
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
ATTR_DEVICETREE,
|
||||||
from ..coresys import CoreSysAttributes
|
ATTR_DISCOVERY,
|
||||||
|
ATTR_DOCKER_API,
|
||||||
|
ATTR_ENVIRONMENT,
|
||||||
|
ATTR_FULL_ACCESS,
|
||||||
|
ATTR_GPIO,
|
||||||
|
ATTR_HASSIO_API,
|
||||||
|
ATTR_HASSIO_ROLE,
|
||||||
|
ATTR_HOMEASSISTANT,
|
||||||
|
ATTR_HOMEASSISTANT_API,
|
||||||
|
ATTR_HOST_DBUS,
|
||||||
|
ATTR_HOST_IPC,
|
||||||
|
ATTR_HOST_NETWORK,
|
||||||
|
ATTR_HOST_PID,
|
||||||
|
ATTR_IMAGE,
|
||||||
|
ATTR_INGRESS,
|
||||||
|
ATTR_INGRESS_ENTRY,
|
||||||
|
ATTR_INGRESS_PORT,
|
||||||
|
ATTR_INGRESS_TOKEN,
|
||||||
|
ATTR_KERNEL_MODULES,
|
||||||
|
ATTR_LEGACY,
|
||||||
|
ATTR_LOCATON,
|
||||||
|
ATTR_MACHINE,
|
||||||
|
ATTR_MAP,
|
||||||
|
ATTR_NAME,
|
||||||
|
ATTR_NETWORK,
|
||||||
|
ATTR_OPTIONS,
|
||||||
|
ATTR_PORTS,
|
||||||
|
ATTR_PORTS_DESCRIPTION,
|
||||||
|
ATTR_PRIVILEGED,
|
||||||
|
ATTR_PROTECTED,
|
||||||
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SCHEMA,
|
||||||
|
ATTR_SERVICES,
|
||||||
|
ATTR_SLUG,
|
||||||
|
ATTR_STARTUP,
|
||||||
|
ATTR_STATE,
|
||||||
|
ATTR_STDIN,
|
||||||
|
ATTR_SYSTEM,
|
||||||
|
ATTR_TIMEOUT,
|
||||||
|
ATTR_TMPFS,
|
||||||
|
ATTR_URL,
|
||||||
|
ATTR_USER,
|
||||||
|
ATTR_UUID,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WEBUI,
|
||||||
|
SECURITY_DEFAULT,
|
||||||
|
SECURITY_DISABLE,
|
||||||
|
SECURITY_PROFILE,
|
||||||
|
STATE_NONE,
|
||||||
|
STATE_STARTED,
|
||||||
|
STATE_STOPPED,
|
||||||
|
)
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..docker.addon import DockerAddon
|
from ..docker.addon import DockerAddon
|
||||||
from ..utils import create_token
|
from ..docker.stats import DockerStats
|
||||||
from ..utils.json import write_json_file, read_json_file
|
from ..exceptions import (
|
||||||
|
AddonsError,
|
||||||
|
AddonsNotSupportedError,
|
||||||
|
DockerAPIError,
|
||||||
|
HostAppArmorError,
|
||||||
|
JsonFileError,
|
||||||
|
)
|
||||||
from ..utils.apparmor import adjust_profile
|
from ..utils.apparmor import adjust_profile
|
||||||
from ..exceptions import HostAppArmorError
|
from ..utils.json import read_json_file, write_json_file
|
||||||
|
from .utils import check_installed, remove_data
|
||||||
|
from .validate import (
|
||||||
|
MACHINE_ALL,
|
||||||
|
RE_SERVICE,
|
||||||
|
RE_VOLUME,
|
||||||
|
SCHEMA_ADDON_SNAPSHOT,
|
||||||
|
validate_options,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -47,20 +112,28 @@ RE_WEBUI = re.compile(
|
|||||||
class Addon(CoreSysAttributes):
|
class Addon(CoreSysAttributes):
|
||||||
"""Hold data for add-on inside Hass.io."""
|
"""Hold data for add-on inside Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys: CoreSys, slug: str):
|
||||||
"""Initialize data holder."""
|
"""Initialize data holder."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.instance = DockerAddon(coresys, slug)
|
self.instance: DockerAddon = DockerAddon(coresys, slug)
|
||||||
|
self._id: str = slug
|
||||||
|
|
||||||
self._id = slug
|
async def load(self) -> None:
|
||||||
|
|
||||||
async def load(self):
|
|
||||||
"""Async initialize of object."""
|
"""Async initialize of object."""
|
||||||
if self.is_installed:
|
if not self.is_installed:
|
||||||
|
return
|
||||||
|
with suppress(DockerAPIError):
|
||||||
await self.instance.attach()
|
await self.instance.attach()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def ip_address(self) -> IPv4Address:
|
||||||
|
"""Return IP of Add-on instance."""
|
||||||
|
if not self.is_installed:
|
||||||
|
return ip_address("0.0.0.0")
|
||||||
|
return self.instance.ip_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slug(self) -> str:
|
||||||
"""Return slug/id of add-on."""
|
"""Return slug/id of add-on."""
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
@@ -75,55 +148,76 @@ class Addon(CoreSysAttributes):
|
|||||||
return self.sys_addons.data
|
return self.sys_addons.data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_installed(self):
|
def is_installed(self) -> bool:
|
||||||
"""Return True if an add-on is installed."""
|
"""Return True if an add-on is installed."""
|
||||||
return self._id in self._data.system
|
return self._id in self._data.system
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_detached(self):
|
def is_detached(self) -> bool:
|
||||||
"""Return True if add-on is detached."""
|
"""Return True if add-on is detached."""
|
||||||
return self._id not in self._data.cache
|
return self._id not in self._data.cache
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return True if this add-on is available on this platform."""
|
"""Return True if this add-on is available on this platform."""
|
||||||
if self.sys_arch not in self.supported_arch:
|
if self.is_detached:
|
||||||
|
addon_data = self._data.system.get(self._id)
|
||||||
|
else:
|
||||||
|
addon_data = self._data.cache.get(self._id)
|
||||||
|
|
||||||
|
# Architecture
|
||||||
|
if not self.sys_arch.is_supported(addon_data[ATTR_ARCH]):
|
||||||
return False
|
return False
|
||||||
if self.sys_machine not in self.supported_machine:
|
|
||||||
|
# Machine / Hardware
|
||||||
|
machine = addon_data.get(ATTR_MACHINE) or MACHINE_ALL
|
||||||
|
if self.sys_machine not in machine:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Home Assistant
|
||||||
|
version = addon_data.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version
|
||||||
|
if StrictVersion(self.sys_homeassistant.version) < StrictVersion(version):
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version_installed(self):
|
def version_installed(self) -> Optional[str]:
|
||||||
"""Return installed version."""
|
"""Return installed version."""
|
||||||
return self._data.user.get(self._id, {}).get(ATTR_VERSION)
|
return self._data.user.get(self._id, {}).get(ATTR_VERSION)
|
||||||
|
|
||||||
def _set_install(self, version):
|
def _set_install(self, image: str, version: str) -> None:
|
||||||
"""Set addon as installed."""
|
"""Set addon as installed."""
|
||||||
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
||||||
self._data.user[self._id] = {
|
self._data.user[self._id] = {
|
||||||
ATTR_OPTIONS: {},
|
ATTR_OPTIONS: {},
|
||||||
ATTR_VERSION: version,
|
ATTR_VERSION: version,
|
||||||
|
ATTR_IMAGE: image,
|
||||||
}
|
}
|
||||||
self._data.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def _set_uninstall(self):
|
def _set_uninstall(self) -> None:
|
||||||
"""Set add-on as uninstalled."""
|
"""Set add-on as uninstalled."""
|
||||||
self._data.system.pop(self._id, None)
|
self._data.system.pop(self._id, None)
|
||||||
self._data.user.pop(self._id, None)
|
self._data.user.pop(self._id, None)
|
||||||
self._data.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def _set_update(self, version):
|
def _set_update(self, image: str, version: str) -> None:
|
||||||
"""Update version of add-on."""
|
"""Update version of add-on."""
|
||||||
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
||||||
self._data.user[self._id][ATTR_VERSION] = version
|
self._data.user[self._id].update({
|
||||||
self._data.save_data()
|
ATTR_VERSION: version,
|
||||||
|
ATTR_IMAGE: image,
|
||||||
|
})
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
def _restore_data(self, user, system):
|
def _restore_data(self, user: Dict[str, Any], system: Dict[str, Any], image: str) -> None:
|
||||||
"""Restore data to add-on."""
|
"""Restore data to add-on."""
|
||||||
self._data.user[self._id] = deepcopy(user)
|
self._data.user[self._id] = deepcopy(user)
|
||||||
self._data.system[self._id] = deepcopy(system)
|
self._data.system[self._id] = deepcopy(system)
|
||||||
self._data.save_data()
|
|
||||||
|
self._data.user[self._id][ATTR_IMAGE] = image
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self):
|
def options(self):
|
||||||
@@ -191,6 +285,20 @@ class Addon(CoreSysAttributes):
|
|||||||
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
|
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_token(self):
|
||||||
|
"""Return access token for Hass.io API."""
|
||||||
|
if self.is_installed:
|
||||||
|
return self._data.user[self._id].get(ATTR_INGRESS_TOKEN)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_entry(self):
|
||||||
|
"""Return ingress external URL."""
|
||||||
|
if self.is_installed and self.with_ingress:
|
||||||
|
return f"/api/hassio_ingress/{self.ingress_token}"
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return description of add-on."""
|
"""Return description of add-on."""
|
||||||
@@ -215,7 +323,7 @@ class Addon(CoreSysAttributes):
|
|||||||
return self._mesh[ATTR_REPOSITORY]
|
return self._mesh[ATTR_REPOSITORY]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_version(self):
|
def latest_version(self):
|
||||||
"""Return version of add-on."""
|
"""Return version of add-on."""
|
||||||
if self._id in self._data.cache:
|
if self._id in self._data.cache:
|
||||||
return self._data.cache[self._id][ATTR_VERSION]
|
return self._data.cache[self._id][ATTR_VERSION]
|
||||||
@@ -257,10 +365,15 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return list of discoverable components/platforms."""
|
"""Return list of discoverable components/platforms."""
|
||||||
return self._mesh.get(ATTR_DISCOVERY, [])
|
return self._mesh.get(ATTR_DISCOVERY, [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ports_description(self):
|
||||||
|
"""Return descriptions of ports."""
|
||||||
|
return self._mesh.get(ATTR_PORTS_DESCRIPTION)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
"""Return ports of add-on."""
|
"""Return ports of add-on."""
|
||||||
if self.host_network or ATTR_PORTS not in self._mesh:
|
if ATTR_PORTS not in self._mesh:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not self.is_installed or \
|
if not self.is_installed or \
|
||||||
@@ -273,13 +386,26 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Set custom ports of add-on."""
|
"""Set custom ports of add-on."""
|
||||||
if value is None:
|
if value is None:
|
||||||
self._data.user[self._id].pop(ATTR_NETWORK, None)
|
self._data.user[self._id].pop(ATTR_NETWORK, None)
|
||||||
else:
|
return
|
||||||
new_ports = {}
|
|
||||||
for container_port, host_port in value.items():
|
|
||||||
if container_port in self._mesh.get(ATTR_PORTS, {}):
|
|
||||||
new_ports[container_port] = host_port
|
|
||||||
|
|
||||||
self._data.user[self._id][ATTR_NETWORK] = new_ports
|
# Secure map ports to value
|
||||||
|
new_ports = {}
|
||||||
|
for container_port, host_port in value.items():
|
||||||
|
if container_port in self._mesh.get(ATTR_PORTS, {}):
|
||||||
|
new_ports[container_port] = host_port
|
||||||
|
|
||||||
|
self._data.user[self._id][ATTR_NETWORK] = new_ports
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_url(self):
|
||||||
|
"""Return URL to ingress url."""
|
||||||
|
if not self.is_installed or not self.with_ingress:
|
||||||
|
return None
|
||||||
|
|
||||||
|
webui = f"/api/hassio_ingress/{self.ingress_token}/"
|
||||||
|
if ATTR_INGRESS_ENTRY in self._mesh:
|
||||||
|
return f"{webui}{self._mesh[ATTR_INGRESS_ENTRY]}"
|
||||||
|
return webui
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def webui(self):
|
def webui(self):
|
||||||
@@ -312,6 +438,17 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
return f"{proto}://[HOST]:{port}{s_suffix}"
|
return f"{proto}://[HOST]:{port}{s_suffix}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_port(self):
|
||||||
|
"""Return Ingress port."""
|
||||||
|
if not self.is_installed or not self.with_ingress:
|
||||||
|
return None
|
||||||
|
|
||||||
|
port = self._mesh[ATTR_INGRESS_PORT]
|
||||||
|
if port == 0:
|
||||||
|
return self.sys_ingress.get_dynamic_port(self.slug)
|
||||||
|
return port
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host_network(self):
|
def host_network(self):
|
||||||
"""Return True if add-on run on host network."""
|
"""Return True if add-on run on host network."""
|
||||||
@@ -396,11 +533,21 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return True if the add-on access use stdin input."""
|
"""Return True if the add-on access use stdin input."""
|
||||||
return self._mesh[ATTR_STDIN]
|
return self._mesh[ATTR_STDIN]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_ingress(self):
|
||||||
|
"""Return True if the add-on access support ingress."""
|
||||||
|
return self._mesh[ATTR_INGRESS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_gpio(self):
|
def with_gpio(self):
|
||||||
"""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_kernel_modules(self):
|
||||||
|
"""Return True if the add-on access to kernel modules."""
|
||||||
|
return self._mesh[ATTR_KERNEL_MODULES]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_full_access(self):
|
def with_full_access(self):
|
||||||
"""Return True if the add-on want full access to hardware."""
|
"""Return True if the add-on want full access to hardware."""
|
||||||
@@ -421,6 +568,11 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return True if the add-on access to audio."""
|
"""Return True if the add-on access to audio."""
|
||||||
return self._mesh[ATTR_AUDIO]
|
return self._mesh[ATTR_AUDIO]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def homeassistant_version(self) -> Optional[str]:
|
||||||
|
"""Return min Home Assistant version they needed by Add-on."""
|
||||||
|
return self._mesh.get(ATTR_HOMEASSISTANT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_output(self):
|
def audio_output(self):
|
||||||
"""Return ALSA config for output or None."""
|
"""Return ALSA config for output or None."""
|
||||||
@@ -491,21 +643,37 @@ class Addon(CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return image name of add-on."""
|
"""Return image name of add-on."""
|
||||||
addon_data = self._mesh
|
if self.is_installed:
|
||||||
|
return self._data.user[self._id].get(ATTR_IMAGE)
|
||||||
|
return self.image_next
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_next(self):
|
||||||
|
"""Return image name for install/update."""
|
||||||
|
if self.is_detached:
|
||||||
|
addon_data = self._data.system.get(self._id)
|
||||||
|
else:
|
||||||
|
addon_data = self._data.cache.get(self._id)
|
||||||
|
return self._get_image(addon_data)
|
||||||
|
|
||||||
|
def _get_image(self, addon_data) -> str:
|
||||||
|
"""Generate image name from data."""
|
||||||
# Repository with Dockerhub images
|
# Repository with Dockerhub images
|
||||||
if ATTR_IMAGE in addon_data:
|
if ATTR_IMAGE in addon_data:
|
||||||
return addon_data[ATTR_IMAGE].format(arch=self.sys_arch)
|
arch = self.sys_arch.match(addon_data[ATTR_ARCH])
|
||||||
|
return addon_data[ATTR_IMAGE].format(arch=arch)
|
||||||
|
|
||||||
# local build
|
# local build
|
||||||
return "{}/{}-addon-{}".format(
|
return (f"{addon_data[ATTR_REPOSITORY]}/"
|
||||||
addon_data[ATTR_REPOSITORY], self.sys_arch,
|
f"{self.sys_arch.default}-"
|
||||||
addon_data[ATTR_SLUG])
|
f"addon-{addon_data[ATTR_SLUG]}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def need_build(self):
|
def need_build(self):
|
||||||
"""Return True if this add-on need a local build."""
|
"""Return True if this add-on need a local build."""
|
||||||
return ATTR_IMAGE not in self._mesh
|
if self.is_detached:
|
||||||
|
return ATTR_IMAGE not in self._data.system.get(self._id)
|
||||||
|
return ATTR_IMAGE not in self._data.cache.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map_volumes(self):
|
def map_volumes(self):
|
||||||
@@ -582,8 +750,8 @@ class Addon(CoreSysAttributes):
|
|||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error("Add-on %s have wrong options: %s", self._id,
|
_LOGGER.error("Add-on %s have wrong options: %s", self._id,
|
||||||
humanize_error(options, ex))
|
humanize_error(options, ex))
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except JsonFileError:
|
||||||
_LOGGER.error("Add-on %s can't write options: %s", self._id, err)
|
_LOGGER.error("Add-on %s can't write options", self._id)
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -610,7 +778,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _install_apparmor(self):
|
async def _install_apparmor(self) -> None:
|
||||||
"""Install or Update AppArmor profile for Add-on."""
|
"""Install or Update AppArmor profile for Add-on."""
|
||||||
exists_local = self.sys_host.apparmor.exists(self.slug)
|
exists_local = self.sys_host.apparmor.exists(self.slug)
|
||||||
exists_addon = self.path_apparmor.exists()
|
exists_addon = self.path_apparmor.exists()
|
||||||
@@ -632,7 +800,7 @@ class Addon(CoreSysAttributes):
|
|||||||
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
|
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self) -> vol.Schema:
|
||||||
"""Create a schema for add-on options."""
|
"""Create a schema for add-on options."""
|
||||||
raw_schema = self._mesh[ATTR_SCHEMA]
|
raw_schema = self._mesh[ATTR_SCHEMA]
|
||||||
|
|
||||||
@@ -640,7 +808,7 @@ class Addon(CoreSysAttributes):
|
|||||||
return vol.Schema(dict)
|
return vol.Schema(dict)
|
||||||
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
||||||
|
|
||||||
def test_update_schema(self):
|
def test_update_schema(self) -> bool:
|
||||||
"""Check if the existing configuration is valid after update."""
|
"""Check if the existing configuration is valid after update."""
|
||||||
if not self.is_installed or self.is_detached:
|
if not self.is_installed or self.is_detached:
|
||||||
return True
|
return True
|
||||||
@@ -670,17 +838,17 @@ class Addon(CoreSysAttributes):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def install(self):
|
async def install(self) -> None:
|
||||||
"""Install an add-on."""
|
"""Install an add-on."""
|
||||||
if not self.available:
|
if not self.available:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Add-on %s not supported on %s with %s architecture",
|
"Add-on %s not supported on %s with %s architecture",
|
||||||
self._id, self.sys_machine, self.sys_arch)
|
self._id, self.sys_machine, self.sys_arch.supported)
|
||||||
return False
|
raise AddonsNotSupportedError()
|
||||||
|
|
||||||
if self.is_installed:
|
if self.is_installed:
|
||||||
_LOGGER.error("Add-on %s is already installed", self._id)
|
_LOGGER.warning("Add-on %s is already installed", self._id)
|
||||||
return False
|
return
|
||||||
|
|
||||||
if not self.path_data.is_dir():
|
if not self.path_data.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -690,17 +858,20 @@ class Addon(CoreSysAttributes):
|
|||||||
# Setup/Fix AppArmor profile
|
# Setup/Fix AppArmor profile
|
||||||
await self._install_apparmor()
|
await self._install_apparmor()
|
||||||
|
|
||||||
if not await self.instance.install(self.last_version):
|
try:
|
||||||
return False
|
await self.instance.install(self.latest_version, self.image_next)
|
||||||
|
except DockerAPIError:
|
||||||
self._set_install(self.last_version)
|
raise AddonsError() from None
|
||||||
return True
|
else:
|
||||||
|
self._set_install(self.image_next, self.latest_version)
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def uninstall(self):
|
async def uninstall(self) -> None:
|
||||||
"""Remove an add-on."""
|
"""Remove an add-on."""
|
||||||
if not await self.instance.remove():
|
try:
|
||||||
return False
|
await self.instance.remove()
|
||||||
|
except DockerAPIError:
|
||||||
|
raise AddonsError() from None
|
||||||
|
|
||||||
if self.path_data.is_dir():
|
if self.path_data.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -717,13 +888,11 @@ class Addon(CoreSysAttributes):
|
|||||||
with suppress(HostAppArmorError):
|
with suppress(HostAppArmorError):
|
||||||
await self.sys_host.apparmor.remove_profile(self.slug)
|
await self.sys_host.apparmor.remove_profile(self.slug)
|
||||||
|
|
||||||
# Remove discovery messages
|
# Cleanup internal data
|
||||||
self.remove_discovery()
|
self.remove_discovery()
|
||||||
|
|
||||||
self._set_uninstall()
|
self._set_uninstall()
|
||||||
return True
|
|
||||||
|
|
||||||
async def state(self):
|
async def state(self) -> str:
|
||||||
"""Return running state of add-on."""
|
"""Return running state of add-on."""
|
||||||
if not self.is_installed:
|
if not self.is_installed:
|
||||||
return STATE_NONE
|
return STATE_NONE
|
||||||
@@ -733,46 +902,58 @@ class Addon(CoreSysAttributes):
|
|||||||
return STATE_STOPPED
|
return STATE_STOPPED
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def start(self):
|
async def start(self) -> None:
|
||||||
"""Set options and start add-on."""
|
"""Set options and start add-on."""
|
||||||
if await self.instance.is_running():
|
if await self.instance.is_running():
|
||||||
_LOGGER.warning("%s already running!", self.slug)
|
_LOGGER.warning("%s already running!", self.slug)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Access Token
|
# Access Token
|
||||||
self._data.user[self._id][ATTR_ACCESS_TOKEN] = create_token()
|
self._data.user[self._id][ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
|
||||||
self._data.save_data()
|
self.save_data()
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
if not self.write_options():
|
if not self.write_options():
|
||||||
return False
|
raise AddonsError()
|
||||||
|
|
||||||
# Sound
|
# Sound
|
||||||
if self.with_audio and not self.write_asound():
|
if self.with_audio and not self.write_asound():
|
||||||
return False
|
raise AddonsError()
|
||||||
|
|
||||||
return await self.instance.run()
|
try:
|
||||||
|
await self.instance.run()
|
||||||
|
except DockerAPIError:
|
||||||
|
raise AddonsError() from None
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
def stop(self):
|
async def stop(self) -> None:
|
||||||
"""Stop add-on.
|
"""Stop add-on."""
|
||||||
|
try:
|
||||||
Return a coroutine.
|
return await self.instance.stop()
|
||||||
"""
|
except DockerAPIError:
|
||||||
return self.instance.stop()
|
raise AddonsError() from None
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def update(self):
|
async def update(self) -> None:
|
||||||
"""Update add-on."""
|
"""Update add-on."""
|
||||||
last_state = await self.state()
|
if self.latest_version == self.version_installed:
|
||||||
|
|
||||||
if self.last_version == self.version_installed:
|
|
||||||
_LOGGER.warning("No update available for add-on %s", self._id)
|
_LOGGER.warning("No update available for add-on %s", self._id)
|
||||||
return False
|
return
|
||||||
|
|
||||||
if not await self.instance.update(self.last_version):
|
# Check if available, Maybe something have changed
|
||||||
return False
|
if not self.available:
|
||||||
self._set_update(self.last_version)
|
_LOGGER.error(
|
||||||
|
"Add-on %s not supported on %s with %s architecture",
|
||||||
|
self._id, self.sys_machine, self.sys_arch.supported)
|
||||||
|
raise AddonsNotSupportedError()
|
||||||
|
|
||||||
|
# Update instance
|
||||||
|
last_state = await self.state()
|
||||||
|
try:
|
||||||
|
await self.instance.update(self.latest_version, self.image_next)
|
||||||
|
except DockerAPIError:
|
||||||
|
raise AddonsError() from None
|
||||||
|
self._set_update(self.image_next, self.latest_version)
|
||||||
|
|
||||||
# Setup/Fix AppArmor profile
|
# Setup/Fix AppArmor profile
|
||||||
await self._install_apparmor()
|
await self._install_apparmor()
|
||||||
@@ -780,16 +961,16 @@ class Addon(CoreSysAttributes):
|
|||||||
# restore state
|
# restore state
|
||||||
if last_state == STATE_STARTED:
|
if last_state == STATE_STARTED:
|
||||||
await self.start()
|
await self.start()
|
||||||
return True
|
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def restart(self):
|
async def restart(self) -> None:
|
||||||
"""Restart add-on."""
|
"""Restart add-on."""
|
||||||
await self.stop()
|
with suppress(AddonsError):
|
||||||
return await self.start()
|
await self.stop()
|
||||||
|
await self.start()
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
def logs(self):
|
def logs(self) -> Awaitable[bytes]:
|
||||||
"""Return add-ons log output.
|
"""Return add-ons log output.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
@@ -797,33 +978,37 @@ class Addon(CoreSysAttributes):
|
|||||||
return self.instance.logs()
|
return self.instance.logs()
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
def stats(self):
|
async def stats(self) -> DockerStats:
|
||||||
"""Return stats of container.
|
"""Return stats of container."""
|
||||||
|
try:
|
||||||
Return a coroutine.
|
return await self.instance.stats()
|
||||||
"""
|
except DockerAPIError:
|
||||||
return self.instance.stats()
|
raise AddonsError() from None
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def rebuild(self):
|
async def rebuild(self) -> None:
|
||||||
"""Perform a rebuild of local build add-on."""
|
"""Perform a rebuild of local build add-on."""
|
||||||
last_state = await self.state()
|
last_state = await self.state()
|
||||||
|
|
||||||
if not self.need_build:
|
if not self.need_build:
|
||||||
_LOGGER.error("Can't rebuild a none local build add-on!")
|
_LOGGER.error("Can't rebuild a image based add-on")
|
||||||
return False
|
raise AddonsNotSupportedError()
|
||||||
|
if self.version_installed != self.latest_version:
|
||||||
|
_LOGGER.error("Version changed, use Update instead Rebuild")
|
||||||
|
raise AddonsError()
|
||||||
|
|
||||||
# remove docker container but not addon config
|
# remove docker container but not addon config
|
||||||
if not await self.instance.remove():
|
try:
|
||||||
return False
|
await self.instance.remove()
|
||||||
|
await self.instance.install(self.version_installed)
|
||||||
if not await self.instance.install(self.version_installed):
|
except DockerAPIError:
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
else:
|
||||||
|
self._set_update(self.image, self.version_installed)
|
||||||
|
|
||||||
# restore state
|
# restore state
|
||||||
if last_state == STATE_STARTED:
|
if last_state == STATE_STARTED:
|
||||||
await self.start()
|
await self.start()
|
||||||
return True
|
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def write_stdin(self, data):
|
async def write_stdin(self, data):
|
||||||
@@ -833,18 +1018,23 @@ class Addon(CoreSysAttributes):
|
|||||||
"""
|
"""
|
||||||
if not self.with_stdin:
|
if not self.with_stdin:
|
||||||
_LOGGER.error("Add-on don't support write to stdin!")
|
_LOGGER.error("Add-on don't support write to stdin!")
|
||||||
return False
|
raise AddonsNotSupportedError()
|
||||||
|
|
||||||
return await self.instance.write_stdin(data)
|
try:
|
||||||
|
return await self.instance.write_stdin(data)
|
||||||
|
except DockerAPIError:
|
||||||
|
raise AddonsError() from None
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def snapshot(self, tar_file):
|
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
||||||
"""Snapshot state of an add-on."""
|
"""Snapshot state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
||||||
# store local image
|
# store local image
|
||||||
if self.need_build and not await \
|
if self.need_build:
|
||||||
self.instance.export_image(Path(temp, 'image.tar')):
|
try:
|
||||||
return False
|
await self.instance.export_image(Path(temp, 'image.tar'))
|
||||||
|
except DockerAPIError:
|
||||||
|
raise AddonsError() from None
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
ATTR_USER: self._data.user.get(self._id, {}),
|
ATTR_USER: self._data.user.get(self._id, {}),
|
||||||
@@ -856,9 +1046,9 @@ class Addon(CoreSysAttributes):
|
|||||||
# Store local configs/state
|
# Store local configs/state
|
||||||
try:
|
try:
|
||||||
write_json_file(Path(temp, 'addon.json'), data)
|
write_json_file(Path(temp, 'addon.json'), data)
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except JsonFileError:
|
||||||
_LOGGER.error("Can't save meta for %s: %s", self._id, err)
|
_LOGGER.error("Can't save meta for %s", self._id)
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Store AppArmor Profile
|
# Store AppArmor Profile
|
||||||
if self.sys_host.apparmor.exists(self.slug):
|
if self.sys_host.apparmor.exists(self.slug):
|
||||||
@@ -867,7 +1057,7 @@ class Addon(CoreSysAttributes):
|
|||||||
self.sys_host.apparmor.backup_profile(self.slug, profile)
|
self.sys_host.apparmor.backup_profile(self.slug, profile)
|
||||||
except HostAppArmorError:
|
except HostAppArmorError:
|
||||||
_LOGGER.error("Can't backup AppArmor profile")
|
_LOGGER.error("Can't backup AppArmor profile")
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# write into tarfile
|
# write into tarfile
|
||||||
def _write_tarfile():
|
def _write_tarfile():
|
||||||
@@ -881,12 +1071,11 @@ class Addon(CoreSysAttributes):
|
|||||||
await self.sys_run_in_executor(_write_tarfile)
|
await self.sys_run_in_executor(_write_tarfile)
|
||||||
except (tarfile.TarError, OSError) as err:
|
except (tarfile.TarError, OSError) as err:
|
||||||
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
_LOGGER.info("Finish snapshot for addon %s", self._id)
|
_LOGGER.info("Finish snapshot for addon %s", self._id)
|
||||||
return True
|
|
||||||
|
|
||||||
async def restore(self, tar_file):
|
async def restore(self, tar_file: tarfile.TarFile) -> None:
|
||||||
"""Restore state of an add-on."""
|
"""Restore state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
||||||
# extract snapshot
|
# extract snapshot
|
||||||
@@ -899,13 +1088,13 @@ class Addon(CoreSysAttributes):
|
|||||||
await self.sys_run_in_executor(_extract_tarfile)
|
await self.sys_run_in_executor(_extract_tarfile)
|
||||||
except tarfile.TarError as err:
|
except tarfile.TarError as err:
|
||||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Read snapshot data
|
# Read snapshot data
|
||||||
try:
|
try:
|
||||||
data = read_json_file(Path(temp, 'addon.json'))
|
data = read_json_file(Path(temp, 'addon.json'))
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except JsonFileError:
|
||||||
_LOGGER.error("Can't read addon.json: %s", err)
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Validate
|
# Validate
|
||||||
try:
|
try:
|
||||||
@@ -913,25 +1102,33 @@ class Addon(CoreSysAttributes):
|
|||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
_LOGGER.error("Can't validate %s, snapshot data: %s",
|
_LOGGER.error("Can't validate %s, snapshot data: %s",
|
||||||
self._id, humanize_error(data, err))
|
self._id, humanize_error(data, err))
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Restore data or reload add-on
|
# Restore local add-on informations
|
||||||
_LOGGER.info("Restore config for addon %s", self._id)
|
_LOGGER.info("Restore config for addon %s", self._id)
|
||||||
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
|
restore_image = self._get_image(data[ATTR_SYSTEM])
|
||||||
|
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM], restore_image)
|
||||||
|
|
||||||
# Check version / restore image
|
# Check version / restore image
|
||||||
version = data[ATTR_VERSION]
|
version = data[ATTR_VERSION]
|
||||||
if not await self.instance.exists():
|
if not await self.instance.exists():
|
||||||
_LOGGER.info("Restore image for addon %s", self._id)
|
_LOGGER.info("Restore/Install image for addon %s", self._id)
|
||||||
|
|
||||||
image_file = Path(temp, 'image.tar')
|
image_file = Path(temp, 'image.tar')
|
||||||
if image_file.is_file():
|
if image_file.is_file():
|
||||||
await self.instance.import_image(image_file, version)
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.import_image(image_file, version)
|
||||||
else:
|
else:
|
||||||
if await self.instance.install(version):
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.install(version, restore_image)
|
||||||
await self.instance.cleanup()
|
await self.instance.cleanup()
|
||||||
|
elif self.instance.version != version or self.legacy:
|
||||||
|
_LOGGER.info("Restore/Update image for addon %s", self._id)
|
||||||
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.update(version, restore_image)
|
||||||
else:
|
else:
|
||||||
await self.instance.stop()
|
with suppress(DockerAPIError):
|
||||||
|
await self.instance.stop()
|
||||||
|
|
||||||
# Restore data
|
# Restore data
|
||||||
def _restore_data():
|
def _restore_data():
|
||||||
@@ -945,7 +1142,7 @@ class Addon(CoreSysAttributes):
|
|||||||
await self.sys_run_in_executor(_restore_data)
|
await self.sys_run_in_executor(_restore_data)
|
||||||
except shutil.Error as err:
|
except shutil.Error as err:
|
||||||
_LOGGER.error("Can't restore origin data: %s", err)
|
_LOGGER.error("Can't restore origin data: %s", err)
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Restore AppArmor
|
# Restore AppArmor
|
||||||
profile_file = Path(temp, 'apparmor.txt')
|
profile_file = Path(temp, 'apparmor.txt')
|
||||||
@@ -955,11 +1152,10 @@ class Addon(CoreSysAttributes):
|
|||||||
self.slug, profile_file)
|
self.slug, profile_file)
|
||||||
except HostAppArmorError:
|
except HostAppArmorError:
|
||||||
_LOGGER.error("Can't restore AppArmor profile")
|
_LOGGER.error("Can't restore AppArmor profile")
|
||||||
return False
|
raise AddonsError() from None
|
||||||
|
|
||||||
# Run add-on
|
# Run add-on
|
||||||
if data[ATTR_STATE] == STATE_STARTED:
|
if data[ATTR_STATE] == STATE_STARTED:
|
||||||
return await self.start()
|
return await self.start()
|
||||||
|
|
||||||
_LOGGER.info("Finish restore for add-on %s", self._id)
|
_LOGGER.info("Finish restore for add-on %s", self._id)
|
||||||
return True
|
|
||||||
|
@@ -1,45 +1,50 @@
|
|||||||
"""Hass.io add-on build environment."""
|
"""Hass.io add-on build environment."""
|
||||||
|
from __future__ import annotations
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from .validate import SCHEMA_BUILD_CONFIG, BASE_IMAGE
|
from ..const import ATTR_ARGS, ATTR_BUILD_FROM, ATTR_SQUASH, META_ADDON
|
||||||
from ..const import ATTR_SQUASH, ATTR_BUILD_FROM, ATTR_ARGS, META_ADDON
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..coresys import CoreSysAttributes
|
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
|
from .validate import SCHEMA_BUILD_CONFIG
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .addon import Addon
|
||||||
|
|
||||||
|
|
||||||
class AddonBuild(JsonConfig, CoreSysAttributes):
|
class AddonBuild(JsonConfig, CoreSysAttributes):
|
||||||
"""Handle build options for add-ons."""
|
"""Handle build options for add-ons."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys: CoreSys, slug: str) -> None:
|
||||||
"""Initialize Hass.io add-on builder."""
|
"""Initialize Hass.io add-on builder."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self._id = slug
|
self._id: str = slug
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
Path(self.addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
Path(self.addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
||||||
|
|
||||||
def save_data(self):
|
def save_data(self):
|
||||||
"""Ignore save function."""
|
"""Ignore save function."""
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addon(self):
|
def addon(self) -> Addon:
|
||||||
"""Return add-on of build data."""
|
"""Return add-on of build data."""
|
||||||
return self.sys_addons.get(self._id)
|
return self.sys_addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_image(self):
|
def base_image(self) -> str:
|
||||||
"""Base images for this add-on."""
|
"""Base images for this add-on."""
|
||||||
return self._data[ATTR_BUILD_FROM].get(
|
return self._data[ATTR_BUILD_FROM].get(
|
||||||
self.sys_arch, BASE_IMAGE[self.sys_arch])
|
self.sys_arch.default,
|
||||||
|
f"homeassistant/{self.sys_arch.default}-base:latest")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def squash(self):
|
def squash(self) -> bool:
|
||||||
"""Return True or False if squash is active."""
|
"""Return True or False if squash is active."""
|
||||||
return self._data[ATTR_SQUASH]
|
return self._data[ATTR_SQUASH]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_args(self):
|
def additional_args(self) -> Dict[str, str]:
|
||||||
"""Return additional Docker build arguments."""
|
"""Return additional Docker build arguments."""
|
||||||
return self._data[ATTR_ARGS]
|
return self._data[ATTR_ARGS]
|
||||||
|
|
||||||
@@ -53,7 +58,7 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
'squash': self.squash,
|
'squash': self.squash,
|
||||||
'labels': {
|
'labels': {
|
||||||
'io.hass.version': version,
|
'io.hass.version': version,
|
||||||
'io.hass.arch': self.sys_arch,
|
'io.hass.arch': self.sys_arch.default,
|
||||||
'io.hass.type': META_ADDON,
|
'io.hass.type': META_ADDON,
|
||||||
'io.hass.name': self._fix_label('name'),
|
'io.hass.name': self._fix_label('name'),
|
||||||
'io.hass.description': self._fix_label('description'),
|
'io.hass.description': self._fix_label('description'),
|
||||||
@@ -61,7 +66,7 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
'buildargs': {
|
'buildargs': {
|
||||||
'BUILD_FROM': self.base_image,
|
'BUILD_FROM': self.base_image,
|
||||||
'BUILD_VERSION': version,
|
'BUILD_VERSION': version,
|
||||||
'BUILD_ARCH': self.sys_arch,
|
'BUILD_ARCH': self.sys_arch.default,
|
||||||
**self.additional_args,
|
**self.additional_args,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +76,7 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def _fix_label(self, label_name):
|
def _fix_label(self, label_name: str) -> str:
|
||||||
"""Remove characters they are not supported."""
|
"""Remove characters they are not supported."""
|
||||||
label = getattr(self.addon, label_name, "")
|
label = getattr(self.addon, label_name, "")
|
||||||
return label.replace("'", "")
|
return label.replace("'", "")
|
||||||
|
@@ -1,19 +1,25 @@
|
|||||||
"""Init file for Hass.io add-on data."""
|
"""Init file for Hass.io add-on data."""
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .utils import extract_hash_from_path
|
|
||||||
from .validate import (
|
|
||||||
SCHEMA_ADDON_CONFIG, SCHEMA_ADDONS_FILE, SCHEMA_REPOSITORY_CONFIG)
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
FILE_HASSIO_ADDONS, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON,
|
ATTR_LOCATON,
|
||||||
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM)
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SLUG,
|
||||||
|
ATTR_SYSTEM,
|
||||||
|
ATTR_USER,
|
||||||
|
FILE_HASSIO_ADDONS,
|
||||||
|
REPOSITORY_CORE,
|
||||||
|
REPOSITORY_LOCAL,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import JsonFileError
|
||||||
from ..utils.json import JsonConfig, read_json_file
|
from ..utils.json import JsonConfig, read_json_file
|
||||||
|
from .utils import extract_hash_from_path
|
||||||
|
from .validate import SCHEMA_ADDON_CONFIG, SCHEMA_ADDONS_FILE, SCHEMA_REPOSITORY_CONFIG
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -54,12 +60,10 @@ class AddonsData(JsonConfig, CoreSysAttributes):
|
|||||||
self._repositories = {}
|
self._repositories = {}
|
||||||
|
|
||||||
# read core repository
|
# read core repository
|
||||||
self._read_addons_folder(
|
self._read_addons_folder(self.sys_config.path_addons_core, REPOSITORY_CORE)
|
||||||
self.sys_config.path_addons_core, REPOSITORY_CORE)
|
|
||||||
|
|
||||||
# read local repository
|
# read local repository
|
||||||
self._read_addons_folder(
|
self._read_addons_folder(self.sys_config.path_addons_local, REPOSITORY_LOCAL)
|
||||||
self.sys_config.path_addons_local, REPOSITORY_LOCAL)
|
|
||||||
|
|
||||||
# add built-in repositories information
|
# add built-in repositories information
|
||||||
self._set_builtin_repositories()
|
self._set_builtin_repositories()
|
||||||
@@ -76,15 +80,12 @@ class AddonsData(JsonConfig, CoreSysAttributes):
|
|||||||
# exists repository json
|
# exists repository json
|
||||||
repository_file = Path(path, "repository.json")
|
repository_file = Path(path, "repository.json")
|
||||||
try:
|
try:
|
||||||
repository_info = SCHEMA_REPOSITORY_CONFIG(
|
repository_info = SCHEMA_REPOSITORY_CONFIG(read_json_file(repository_file))
|
||||||
read_json_file(repository_file)
|
except JsonFileError:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Can't read repository information from %s", repository_file
|
||||||
)
|
)
|
||||||
|
|
||||||
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
|
|
||||||
_LOGGER.warning("Can't read repository information from %s",
|
|
||||||
repository_file)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
_LOGGER.warning("Repository parse error %s", repository_file)
|
_LOGGER.warning("Repository parse error %s", repository_file)
|
||||||
return
|
return
|
||||||
@@ -98,39 +99,38 @@ class AddonsData(JsonConfig, CoreSysAttributes):
|
|||||||
for addon in path.glob("**/config.json"):
|
for addon in path.glob("**/config.json"):
|
||||||
try:
|
try:
|
||||||
addon_config = read_json_file(addon)
|
addon_config = read_json_file(addon)
|
||||||
|
except JsonFileError:
|
||||||
|
_LOGGER.warning("Can't read %s from repository %s", addon, repository)
|
||||||
|
continue
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
|
try:
|
||||||
addon_config = SCHEMA_ADDON_CONFIG(addon_config)
|
addon_config = SCHEMA_ADDON_CONFIG(addon_config)
|
||||||
|
|
||||||
# Generate slug
|
|
||||||
addon_slug = "{}_{}".format(
|
|
||||||
repository, addon_config[ATTR_SLUG])
|
|
||||||
|
|
||||||
# store
|
|
||||||
addon_config[ATTR_REPOSITORY] = repository
|
|
||||||
addon_config[ATTR_LOCATON] = str(addon.parent)
|
|
||||||
self._cache[addon_slug] = addon_config
|
|
||||||
|
|
||||||
except (OSError, json.JSONDecodeError):
|
|
||||||
_LOGGER.warning("Can't read %s", addon)
|
|
||||||
|
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.warning("Can't read %s: %s", addon,
|
_LOGGER.warning(
|
||||||
humanize_error(addon_config, ex))
|
"Can't read %s: %s", addon, humanize_error(addon_config, ex)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Generate slug
|
||||||
|
addon_slug = "{}_{}".format(repository, addon_config[ATTR_SLUG])
|
||||||
|
|
||||||
|
# store
|
||||||
|
addon_config[ATTR_REPOSITORY] = repository
|
||||||
|
addon_config[ATTR_LOCATON] = str(addon.parent)
|
||||||
|
self._cache[addon_slug] = addon_config
|
||||||
|
|
||||||
def _set_builtin_repositories(self):
|
def _set_builtin_repositories(self):
|
||||||
"""Add local built-in repository into dataset."""
|
"""Add local built-in repository into dataset."""
|
||||||
try:
|
try:
|
||||||
builtin_file = Path(__file__).parent.joinpath('built-in.json')
|
builtin_file = Path(__file__).parent.joinpath("built-in.json")
|
||||||
builtin_data = read_json_file(builtin_file)
|
builtin_data = read_json_file(builtin_file)
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except JsonFileError:
|
||||||
_LOGGER.warning("Can't read built-in json: %s", err)
|
_LOGGER.warning("Can't read built-in json")
|
||||||
return
|
return
|
||||||
|
|
||||||
# core repository
|
# core repository
|
||||||
self._repositories[REPOSITORY_CORE] = \
|
self._repositories[REPOSITORY_CORE] = builtin_data[REPOSITORY_CORE]
|
||||||
builtin_data[REPOSITORY_CORE]
|
|
||||||
|
|
||||||
# local repository
|
# local repository
|
||||||
self._repositories[REPOSITORY_LOCAL] = \
|
self._repositories[REPOSITORY_LOCAL] = builtin_data[REPOSITORY_LOCAL]
|
||||||
builtin_data[REPOSITORY_LOCAL]
|
|
||||||
|
@@ -1,20 +1,35 @@
|
|||||||
"""Util add-ons functions."""
|
"""Util add-ons functions."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
SECURITY_DISABLE, SECURITY_PROFILE, PRIVILEGED_NET_ADMIN,
|
PRIVILEGED_DAC_READ_SEARCH,
|
||||||
PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE,
|
PRIVILEGED_NET_ADMIN,
|
||||||
PRIVILEGED_DAC_READ_SEARCH, ROLE_ADMIN, ROLE_MANAGER)
|
PRIVILEGED_SYS_ADMIN,
|
||||||
|
PRIVILEGED_SYS_MODULE,
|
||||||
|
PRIVILEGED_SYS_PTRACE,
|
||||||
|
PRIVILEGED_SYS_RAWIO,
|
||||||
|
ROLE_ADMIN,
|
||||||
|
ROLE_MANAGER,
|
||||||
|
SECURITY_DISABLE,
|
||||||
|
SECURITY_PROFILE,
|
||||||
|
)
|
||||||
|
from ..exceptions import AddonsNotSupportedError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .addon import Addon
|
||||||
|
|
||||||
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):
|
def rating_security(addon: Addon) -> int:
|
||||||
"""Return 1-6 for security rating.
|
"""Return 1-6 for security rating.
|
||||||
|
|
||||||
1 = not secure
|
1 = not secure
|
||||||
@@ -33,10 +48,17 @@ def rating_security(addon):
|
|||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
# Privileged options
|
# Privileged options
|
||||||
if any(privilege in addon.privileged
|
if any(
|
||||||
for privilege in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
|
privilege in addon.privileged
|
||||||
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE,
|
for privilege in (
|
||||||
PRIVILEGED_DAC_READ_SEARCH)):
|
PRIVILEGED_NET_ADMIN,
|
||||||
|
PRIVILEGED_SYS_ADMIN,
|
||||||
|
PRIVILEGED_SYS_RAWIO,
|
||||||
|
PRIVILEGED_SYS_PTRACE,
|
||||||
|
PRIVILEGED_SYS_MODULE,
|
||||||
|
PRIVILEGED_DAC_READ_SEARCH,
|
||||||
|
)
|
||||||
|
):
|
||||||
rating += -1
|
rating += -1
|
||||||
|
|
||||||
# API Hass.io role
|
# API Hass.io role
|
||||||
@@ -64,39 +86,39 @@ def rating_security(addon):
|
|||||||
return max(min(6, rating), 1)
|
return max(min(6, rating), 1)
|
||||||
|
|
||||||
|
|
||||||
def get_hash_from_repository(name):
|
def get_hash_from_repository(name: str) -> str:
|
||||||
"""Generate a hash from repository."""
|
"""Generate a hash from repository."""
|
||||||
key = name.lower().encode()
|
key = name.lower().encode()
|
||||||
return hashlib.sha1(key).hexdigest()[:8]
|
return hashlib.sha1(key).hexdigest()[:8]
|
||||||
|
|
||||||
|
|
||||||
def extract_hash_from_path(path):
|
def extract_hash_from_path(path: Path) -> str:
|
||||||
"""Extract repo id from path."""
|
"""Extract repo id from path."""
|
||||||
repo_dir = path.parts[-1]
|
repository_dir = path.parts[-1]
|
||||||
|
|
||||||
if not RE_SHA1.match(repo_dir):
|
if not RE_SHA1.match(repository_dir):
|
||||||
return get_hash_from_repository(repo_dir)
|
return get_hash_from_repository(repository_dir)
|
||||||
return repo_dir
|
return repository_dir
|
||||||
|
|
||||||
|
|
||||||
def check_installed(method):
|
def check_installed(method):
|
||||||
"""Wrap function with check if add-on is installed."""
|
"""Wrap function with check if add-on is installed."""
|
||||||
|
|
||||||
async def wrap_check(addon, *args, **kwargs):
|
async def wrap_check(addon, *args, **kwargs):
|
||||||
"""Return False if not installed or the function."""
|
"""Return False if not installed or the function."""
|
||||||
if not addon.is_installed:
|
if not addon.is_installed:
|
||||||
_LOGGER.error("Addon %s is not installed", addon.slug)
|
_LOGGER.error("Addon %s is not installed", addon.slug)
|
||||||
return False
|
raise AddonsNotSupportedError()
|
||||||
return await method(addon, *args, **kwargs)
|
return await method(addon, *args, **kwargs)
|
||||||
|
|
||||||
return wrap_check
|
return wrap_check
|
||||||
|
|
||||||
|
|
||||||
async def remove_data(folder):
|
async def remove_data(folder: Path) -> None:
|
||||||
"""Remove folder and reset privileged."""
|
"""Remove folder and reset privileged."""
|
||||||
try:
|
try:
|
||||||
proc = await asyncio.create_subprocess_exec(
|
proc = await asyncio.create_subprocess_exec(
|
||||||
"rm", "-rf", str(folder),
|
"rm", "-rf", str(folder), stdout=asyncio.subprocess.DEVNULL
|
||||||
stdout=asyncio.subprocess.DEVNULL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_, error_msg = await proc.communicate()
|
_, error_msg = await proc.communicate()
|
||||||
|
@@ -1,33 +1,95 @@
|
|||||||
"""Validate add-ons options schema."""
|
"""Validate add-ons options schema."""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import secrets
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP,
|
ARCH_ALL,
|
||||||
ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, STARTUP_ONCE,
|
ATTR_ACCESS_TOKEN,
|
||||||
STARTUP_SYSTEM, STARTUP_SERVICES, STARTUP_APPLICATION, STARTUP_INITIALIZE,
|
ATTR_APPARMOR,
|
||||||
BOOT_AUTO, BOOT_MANUAL, ATTR_SCHEMA, ATTR_IMAGE, ATTR_URL, ATTR_MAINTAINER,
|
ATTR_ARCH,
|
||||||
ATTR_ARCH, ATTR_DEVICES, ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ARCH_ARMHF,
|
ATTR_ARGS,
|
||||||
ARCH_AARCH64, ARCH_AMD64, ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED,
|
ATTR_AUDIO,
|
||||||
ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED,
|
ATTR_AUDIO_INPUT,
|
||||||
ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK, ATTR_UUID,
|
ATTR_AUDIO_OUTPUT,
|
||||||
ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_HOST_IPC,
|
ATTR_AUTH_API,
|
||||||
ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH,
|
ATTR_AUTO_UART,
|
||||||
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
|
ATTR_AUTO_UPDATE,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
ATTR_BOOT,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
ATTR_BUILD_FROM,
|
||||||
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
ATTR_DESCRIPTON,
|
||||||
ATTR_MACHINE, ATTR_AUTH_API,
|
ATTR_DEVICES,
|
||||||
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
ATTR_DEVICETREE,
|
||||||
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
ATTR_DISCOVERY,
|
||||||
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE, PRIVILEGED_DAC_READ_SEARCH,
|
ATTR_DOCKER_API,
|
||||||
ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN, ROLE_BACKUP)
|
ATTR_ENVIRONMENT,
|
||||||
|
ATTR_FULL_ACCESS,
|
||||||
|
ATTR_GPIO,
|
||||||
|
ATTR_HASSIO_API,
|
||||||
|
ATTR_HASSIO_ROLE,
|
||||||
|
ATTR_HOMEASSISTANT,
|
||||||
|
ATTR_HOMEASSISTANT_API,
|
||||||
|
ATTR_HOST_DBUS,
|
||||||
|
ATTR_HOST_IPC,
|
||||||
|
ATTR_HOST_NETWORK,
|
||||||
|
ATTR_HOST_PID,
|
||||||
|
ATTR_IMAGE,
|
||||||
|
ATTR_INGRESS,
|
||||||
|
ATTR_INGRESS_ENTRY,
|
||||||
|
ATTR_INGRESS_PORT,
|
||||||
|
ATTR_INGRESS_TOKEN,
|
||||||
|
ATTR_KERNEL_MODULES,
|
||||||
|
ATTR_LEGACY,
|
||||||
|
ATTR_LOCATON,
|
||||||
|
ATTR_MACHINE,
|
||||||
|
ATTR_MAINTAINER,
|
||||||
|
ATTR_MAP,
|
||||||
|
ATTR_NAME,
|
||||||
|
ATTR_NETWORK,
|
||||||
|
ATTR_OPTIONS,
|
||||||
|
ATTR_PORTS,
|
||||||
|
ATTR_PORTS_DESCRIPTION,
|
||||||
|
ATTR_PRIVILEGED,
|
||||||
|
ATTR_PROTECTED,
|
||||||
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SCHEMA,
|
||||||
|
ATTR_SERVICES,
|
||||||
|
ATTR_SLUG,
|
||||||
|
ATTR_SQUASH,
|
||||||
|
ATTR_STARTUP,
|
||||||
|
ATTR_STATE,
|
||||||
|
ATTR_STDIN,
|
||||||
|
ATTR_SYSTEM,
|
||||||
|
ATTR_TIMEOUT,
|
||||||
|
ATTR_TMPFS,
|
||||||
|
ATTR_URL,
|
||||||
|
ATTR_USER,
|
||||||
|
ATTR_UUID,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WEBUI,
|
||||||
|
BOOT_AUTO,
|
||||||
|
BOOT_MANUAL,
|
||||||
|
PRIVILEGED_ALL,
|
||||||
|
ROLE_ALL,
|
||||||
|
ROLE_DEFAULT,
|
||||||
|
STARTUP_ALL,
|
||||||
|
STARTUP_APPLICATION,
|
||||||
|
STARTUP_SERVICES,
|
||||||
|
STATE_STARTED,
|
||||||
|
STATE_STOPPED,
|
||||||
|
)
|
||||||
|
from ..discovery.validate import valid_discovery_service
|
||||||
from ..validate import (
|
from ..validate import (
|
||||||
NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE, UUID_MATCH, SHA256)
|
ALSA_DEVICE,
|
||||||
from ..services.validate import DISCOVERY_SERVICES
|
DOCKER_PORTS,
|
||||||
|
DOCKER_PORTS_DESCRIPTION,
|
||||||
|
NETWORK_PORT,
|
||||||
|
TOKEN,
|
||||||
|
UUID_MATCH,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -53,53 +115,20 @@ RE_SCHEMA_ELEMENT = re.compile(
|
|||||||
r")\??$"
|
r")\??$"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
RE_DOCKER_IMAGE = re.compile(
|
||||||
|
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)$")
|
||||||
|
RE_DOCKER_IMAGE_BUILD = re.compile(
|
||||||
|
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$")
|
||||||
|
|
||||||
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
|
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
|
||||||
|
|
||||||
ARCH_ALL = [
|
|
||||||
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
|
|
||||||
]
|
|
||||||
|
|
||||||
MACHINE_ALL = [
|
MACHINE_ALL = [
|
||||||
'intel-nuc',
|
'intel-nuc', 'odroid-c2', 'odroid-xu', 'orangepi-prime', 'qemux86',
|
||||||
'odroid-c2', 'odroid-xu',
|
'qemux86-64', 'qemuarm', 'qemuarm-64', 'raspberrypi', 'raspberrypi2',
|
||||||
'orangepi-prime',
|
'raspberrypi3', 'raspberrypi3-64', 'tinker',
|
||||||
'qemux86', 'qemux86-64', 'qemuarm', 'qemuarm-64',
|
|
||||||
'raspberrypi', 'raspberrypi2', 'raspberrypi3', 'raspberrypi3-64',
|
|
||||||
'tinker',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
STARTUP_ALL = [
|
|
||||||
STARTUP_ONCE, STARTUP_INITIALIZE, STARTUP_SYSTEM, STARTUP_SERVICES,
|
|
||||||
STARTUP_APPLICATION
|
|
||||||
]
|
|
||||||
|
|
||||||
PRIVILEGED_ALL = [
|
|
||||||
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_DAC_READ_SEARCH,
|
|
||||||
]
|
|
||||||
|
|
||||||
ROLE_ALL = [
|
|
||||||
ROLE_DEFAULT,
|
|
||||||
ROLE_HOMEASSISTANT,
|
|
||||||
ROLE_BACKUP,
|
|
||||||
ROLE_MANAGER,
|
|
||||||
ROLE_ADMIN,
|
|
||||||
]
|
|
||||||
|
|
||||||
BASE_IMAGE = {
|
|
||||||
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
|
||||||
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
|
|
||||||
ARCH_I386: "homeassistant/i386-base:latest",
|
|
||||||
ARCH_AMD64: "homeassistant/amd64-base:latest",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _simple_startup(value):
|
def _simple_startup(value):
|
||||||
"""Simple startup schema."""
|
"""Simple startup schema."""
|
||||||
@@ -116,16 +145,21 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||||
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_URL): vol.Url(),
|
vol.Required(ATTR_ARCH): [vol.In(ARCH_ALL)],
|
||||||
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
|
|
||||||
vol.Optional(ATTR_MACHINE): [vol.In(MACHINE_ALL)],
|
vol.Optional(ATTR_MACHINE): [vol.In(MACHINE_ALL)],
|
||||||
|
vol.Optional(ATTR_URL): vol.Url(),
|
||||||
vol.Required(ATTR_STARTUP):
|
vol.Required(ATTR_STARTUP):
|
||||||
vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
||||||
vol.Required(ATTR_BOOT):
|
vol.Required(ATTR_BOOT):
|
||||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||||
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
|
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
|
||||||
|
vol.Optional(ATTR_PORTS_DESCRIPTION): DOCKER_PORTS_DESCRIPTION,
|
||||||
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_INGRESS, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(NETWORK_PORT, vol.Equal(0)),
|
||||||
|
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
||||||
@@ -142,6 +176,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
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(),
|
||||||
|
vol.Optional(ATTR_KERNEL_MODULES, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
|
vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(),
|
||||||
@@ -150,7 +185,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTH_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.In(DISCOVERY_SERVICES)],
|
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
||||||
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
||||||
@@ -163,7 +198,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
}))
|
}))
|
||||||
}), False),
|
}), False),
|
||||||
vol.Optional(ATTR_IMAGE):
|
vol.Optional(ATTR_IMAGE):
|
||||||
vol.Match(r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)$"),
|
vol.Match(RE_DOCKER_IMAGE),
|
||||||
vol.Optional(ATTR_TIMEOUT, default=10):
|
vol.Optional(ATTR_TIMEOUT, default=10):
|
||||||
vol.All(vol.Coerce(int), vol.Range(min=10, max=120)),
|
vol.All(vol.Coerce(int), vol.Range(min=10, max=120)),
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
@@ -179,8 +214,8 @@ SCHEMA_REPOSITORY_CONFIG = vol.Schema({
|
|||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_BUILD_CONFIG = vol.Schema({
|
SCHEMA_BUILD_CONFIG = vol.Schema({
|
||||||
vol.Optional(ATTR_BUILD_FROM, default=BASE_IMAGE): vol.Schema({
|
vol.Optional(ATTR_BUILD_FROM, default=dict): vol.Schema({
|
||||||
vol.In(ARCH_ALL): vol.Match(r"(?:^[\w{}]+/)?[\-\w{}]+:[\.\-\w{}]+$"),
|
vol.In(ARCH_ALL): vol.Match(RE_DOCKER_IMAGE_BUILD),
|
||||||
}),
|
}),
|
||||||
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ARGS, default=dict): vol.Schema({
|
vol.Optional(ATTR_ARGS, default=dict): vol.Schema({
|
||||||
@@ -192,8 +227,10 @@ SCHEMA_BUILD_CONFIG = vol.Schema({
|
|||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_ADDON_USER = vol.Schema({
|
SCHEMA_ADDON_USER = vol.Schema({
|
||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_IMAGE): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): SHA256,
|
vol.Optional(ATTR_ACCESS_TOKEN): TOKEN,
|
||||||
|
vol.Optional(ATTR_INGRESS_TOKEN, default=secrets.token_urlsafe): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
||||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_BOOT):
|
vol.Optional(ATTR_BOOT):
|
||||||
|
@@ -1,23 +1,25 @@
|
|||||||
"""Init file for Hass.io RESTful API."""
|
"""Init file for Hass.io RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .addons import APIAddons
|
from .addons import APIAddons
|
||||||
from .auth import APIAuth
|
from .auth import APIAuth
|
||||||
from .discovery import APIDiscovery
|
from .discovery import APIDiscovery
|
||||||
from .homeassistant import APIHomeAssistant
|
|
||||||
from .hardware import APIHardware
|
from .hardware import APIHardware
|
||||||
from .host import APIHost
|
|
||||||
from .hassos import APIHassOS
|
from .hassos import APIHassOS
|
||||||
|
from .homeassistant import APIHomeAssistant
|
||||||
|
from .host import APIHost
|
||||||
from .info import APIInfo
|
from .info import APIInfo
|
||||||
|
from .ingress import APIIngress
|
||||||
from .proxy import APIProxy
|
from .proxy import APIProxy
|
||||||
from .supervisor import APISupervisor
|
|
||||||
from .snapshots import APISnapshots
|
|
||||||
from .services import APIServices
|
|
||||||
from .security import SecurityMiddleware
|
from .security import SecurityMiddleware
|
||||||
from ..coresys import CoreSysAttributes
|
from .services import APIServices
|
||||||
|
from .snapshots import APISnapshots
|
||||||
|
from .supervisor import APISupervisor
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -25,18 +27,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class RestAPI(CoreSysAttributes):
|
class RestAPI(CoreSysAttributes):
|
||||||
"""Handle RESTful API for Hass.io."""
|
"""Handle RESTful API for Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize Docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.security = SecurityMiddleware(coresys)
|
self.security: SecurityMiddleware = SecurityMiddleware(coresys)
|
||||||
self.webapp = web.Application(
|
self.webapp: web.Application = web.Application(
|
||||||
middlewares=[self.security.token_validation], loop=coresys.loop)
|
middlewares=[self.security.token_validation])
|
||||||
|
|
||||||
# service stuff
|
# service stuff
|
||||||
self._runner = web.AppRunner(self.webapp)
|
self._runner: web.AppRunner = web.AppRunner(self.webapp)
|
||||||
self._site = None
|
self._site: Optional[web.TCPSite] = None
|
||||||
|
|
||||||
async def load(self):
|
async def load(self) -> None:
|
||||||
"""Register REST API Calls."""
|
"""Register REST API Calls."""
|
||||||
self._register_supervisor()
|
self._register_supervisor()
|
||||||
self._register_host()
|
self._register_host()
|
||||||
@@ -46,13 +48,14 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_proxy()
|
self._register_proxy()
|
||||||
self._register_panel()
|
self._register_panel()
|
||||||
self._register_addons()
|
self._register_addons()
|
||||||
|
self._register_ingress()
|
||||||
self._register_snapshots()
|
self._register_snapshots()
|
||||||
self._register_discovery()
|
self._register_discovery()
|
||||||
self._register_services()
|
self._register_services()
|
||||||
self._register_info()
|
self._register_info()
|
||||||
self._register_auth()
|
self._register_auth()
|
||||||
|
|
||||||
def _register_host(self):
|
def _register_host(self) -> None:
|
||||||
"""Register hostcontrol functions."""
|
"""Register hostcontrol functions."""
|
||||||
api_host = APIHost()
|
api_host = APIHost()
|
||||||
api_host.coresys = self.coresys
|
api_host.coresys = self.coresys
|
||||||
@@ -66,13 +69,13 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/host/services', api_host.services),
|
web.get('/host/services', api_host.services),
|
||||||
web.post('/host/services/{service}/stop', api_host.service_stop),
|
web.post('/host/services/{service}/stop', api_host.service_stop),
|
||||||
web.post('/host/services/{service}/start', api_host.service_start),
|
web.post('/host/services/{service}/start', api_host.service_start),
|
||||||
web.post(
|
web.post('/host/services/{service}/restart',
|
||||||
'/host/services/{service}/restart', api_host.service_restart),
|
api_host.service_restart),
|
||||||
web.post(
|
web.post('/host/services/{service}/reload',
|
||||||
'/host/services/{service}/reload', api_host.service_reload),
|
api_host.service_reload),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_hassos(self):
|
def _register_hassos(self) -> None:
|
||||||
"""Register HassOS functions."""
|
"""Register HassOS functions."""
|
||||||
api_hassos = APIHassOS()
|
api_hassos = APIHassOS()
|
||||||
api_hassos.coresys = self.coresys
|
api_hassos.coresys = self.coresys
|
||||||
@@ -84,7 +87,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.post('/hassos/config/sync', api_hassos.config_sync),
|
web.post('/hassos/config/sync', api_hassos.config_sync),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_hardware(self):
|
def _register_hardware(self) -> None:
|
||||||
"""Register hardware functions."""
|
"""Register hardware functions."""
|
||||||
api_hardware = APIHardware()
|
api_hardware = APIHardware()
|
||||||
api_hardware.coresys = self.coresys
|
api_hardware.coresys = self.coresys
|
||||||
@@ -94,7 +97,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/hardware/audio', api_hardware.audio),
|
web.get('/hardware/audio', api_hardware.audio),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_info(self):
|
def _register_info(self) -> None:
|
||||||
"""Register info functions."""
|
"""Register info functions."""
|
||||||
api_info = APIInfo()
|
api_info = APIInfo()
|
||||||
api_info.coresys = self.coresys
|
api_info.coresys = self.coresys
|
||||||
@@ -103,7 +106,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/info', api_info.info),
|
web.get('/info', api_info.info),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_auth(self):
|
def _register_auth(self) -> None:
|
||||||
"""Register auth functions."""
|
"""Register auth functions."""
|
||||||
api_auth = APIAuth()
|
api_auth = APIAuth()
|
||||||
api_auth.coresys = self.coresys
|
api_auth.coresys = self.coresys
|
||||||
@@ -112,7 +115,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.post('/auth', api_auth.auth),
|
web.post('/auth', api_auth.auth),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_supervisor(self):
|
def _register_supervisor(self) -> None:
|
||||||
"""Register Supervisor functions."""
|
"""Register Supervisor functions."""
|
||||||
api_supervisor = APISupervisor()
|
api_supervisor = APISupervisor()
|
||||||
api_supervisor.coresys = self.coresys
|
api_supervisor.coresys = self.coresys
|
||||||
@@ -127,7 +130,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.post('/supervisor/options', api_supervisor.options),
|
web.post('/supervisor/options', api_supervisor.options),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_homeassistant(self):
|
def _register_homeassistant(self) -> None:
|
||||||
"""Register Home Assistant functions."""
|
"""Register Home Assistant functions."""
|
||||||
api_hass = APIHomeAssistant()
|
api_hass = APIHomeAssistant()
|
||||||
api_hass.coresys = self.coresys
|
api_hass.coresys = self.coresys
|
||||||
@@ -142,9 +145,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.post('/homeassistant/stop', api_hass.stop),
|
web.post('/homeassistant/stop', api_hass.stop),
|
||||||
web.post('/homeassistant/start', api_hass.start),
|
web.post('/homeassistant/start', api_hass.start),
|
||||||
web.post('/homeassistant/check', api_hass.check),
|
web.post('/homeassistant/check', api_hass.check),
|
||||||
|
web.post('/homeassistant/rebuild', api_hass.rebuild),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_proxy(self):
|
def _register_proxy(self) -> None:
|
||||||
"""Register Home Assistant API Proxy."""
|
"""Register Home Assistant API Proxy."""
|
||||||
api_proxy = APIProxy()
|
api_proxy = APIProxy()
|
||||||
api_proxy.coresys = self.coresys
|
api_proxy.coresys = self.coresys
|
||||||
@@ -158,7 +162,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/homeassistant/api/', api_proxy.api),
|
web.get('/homeassistant/api/', api_proxy.api),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_addons(self):
|
def _register_addons(self) -> None:
|
||||||
"""Register Add-on functions."""
|
"""Register Add-on functions."""
|
||||||
api_addons = APIAddons()
|
api_addons = APIAddons()
|
||||||
api_addons.coresys = self.coresys
|
api_addons.coresys = self.coresys
|
||||||
@@ -184,7 +188,17 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/addons/{addon}/stats', api_addons.stats),
|
web.get('/addons/{addon}/stats', api_addons.stats),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_snapshots(self):
|
def _register_ingress(self) -> None:
|
||||||
|
"""Register Ingress functions."""
|
||||||
|
api_ingress = APIIngress()
|
||||||
|
api_ingress.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes([
|
||||||
|
web.post('/ingress/session', api_ingress.create_session),
|
||||||
|
web.view('/ingress/{token}/{path:.*}', api_ingress.handler),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _register_snapshots(self) -> None:
|
||||||
"""Register snapshots functions."""
|
"""Register snapshots functions."""
|
||||||
api_snapshots = APISnapshots()
|
api_snapshots = APISnapshots()
|
||||||
api_snapshots.coresys = self.coresys
|
api_snapshots.coresys = self.coresys
|
||||||
@@ -204,7 +218,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/snapshots/{snapshot}/download', api_snapshots.download),
|
web.get('/snapshots/{snapshot}/download', api_snapshots.download),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_services(self):
|
def _register_services(self) -> None:
|
||||||
"""Register services functions."""
|
"""Register services functions."""
|
||||||
api_services = APIServices()
|
api_services = APIServices()
|
||||||
api_services.coresys = self.coresys
|
api_services.coresys = self.coresys
|
||||||
@@ -216,7 +230,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.delete('/services/{service}', api_services.del_service),
|
web.delete('/services/{service}', api_services.del_service),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_discovery(self):
|
def _register_discovery(self) -> None:
|
||||||
"""Register discovery functions."""
|
"""Register discovery functions."""
|
||||||
api_discovery = APIDiscovery()
|
api_discovery = APIDiscovery()
|
||||||
api_discovery.coresys = self.coresys
|
api_discovery.coresys = self.coresys
|
||||||
@@ -224,12 +238,11 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self.webapp.add_routes([
|
self.webapp.add_routes([
|
||||||
web.get('/discovery', api_discovery.list),
|
web.get('/discovery', api_discovery.list),
|
||||||
web.get('/discovery/{uuid}', api_discovery.get_discovery),
|
web.get('/discovery/{uuid}', api_discovery.get_discovery),
|
||||||
web.delete('/discovery/{uuid}',
|
web.delete('/discovery/{uuid}', api_discovery.del_discovery),
|
||||||
api_discovery.del_discovery),
|
|
||||||
web.post('/discovery', api_discovery.set_discovery),
|
web.post('/discovery', api_discovery.set_discovery),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_panel(self):
|
def _register_panel(self) -> None:
|
||||||
"""Register panel for Home Assistant."""
|
"""Register panel for Home Assistant."""
|
||||||
panel_dir = Path(__file__).parent.joinpath("panel")
|
panel_dir = Path(__file__).parent.joinpath("panel")
|
||||||
|
|
||||||
@@ -239,8 +252,8 @@ class RestAPI(CoreSysAttributes):
|
|||||||
return lambda request: web.FileResponse(path)
|
return lambda request: web.FileResponse(path)
|
||||||
|
|
||||||
# This route is for backwards compatibility with HA < 0.58
|
# This route is for backwards compatibility with HA < 0.58
|
||||||
self.webapp.add_routes([
|
self.webapp.add_routes(
|
||||||
web.get('/panel', create_response('hassio-main-es5'))])
|
[web.get('/panel', create_response('hassio-main-es5'))])
|
||||||
|
|
||||||
# This route is for backwards compatibility with HA 0.58 - 0.61
|
# This route is for backwards compatibility with HA 0.58 - 0.61
|
||||||
self.webapp.add_routes([
|
self.webapp.add_routes([
|
||||||
@@ -257,7 +270,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
# This route is for HA > 0.70
|
# This route is for HA > 0.70
|
||||||
self.webapp.add_routes([web.static('/app', panel_dir)])
|
self.webapp.add_routes([web.static('/app', panel_dir)])
|
||||||
|
|
||||||
async def start(self):
|
async def start(self) -> None:
|
||||||
"""Run RESTful API webserver."""
|
"""Run RESTful API webserver."""
|
||||||
await self._runner.setup()
|
await self._runner.setup()
|
||||||
self._site = web.TCPSite(
|
self._site = web.TCPSite(
|
||||||
@@ -266,12 +279,12 @@ class RestAPI(CoreSysAttributes):
|
|||||||
try:
|
try:
|
||||||
await self._site.start()
|
await self._site.start()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.fatal(
|
_LOGGER.fatal("Failed to create HTTP server at 0.0.0.0:80 -> %s",
|
||||||
"Failed to create HTTP server at 0.0.0.0:80 -> %s", err)
|
err)
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor)
|
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor)
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self) -> None:
|
||||||
"""Stop RESTful API webserver."""
|
"""Stop RESTful API webserver."""
|
||||||
if not self._site:
|
if not self._site:
|
||||||
return
|
return
|
||||||
|
@@ -1,30 +1,91 @@
|
|||||||
"""Init file for Hass.io Home Assistant RESTful API."""
|
"""Init file for Hass.io Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict, List
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
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 ..addons.addon import Addon
|
||||||
from ..addons.utils import rating_security
|
from ..addons.utils import rating_security
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS,
|
ATTR_ADDONS,
|
||||||
ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY,
|
ATTR_APPARMOR,
|
||||||
ATTR_BUILD, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_HOST_NETWORK, ATTR_SLUG,
|
ATTR_ARCH,
|
||||||
ATTR_SOURCE, ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_ARCH, ATTR_MAINTAINER,
|
ATTR_AUDIO,
|
||||||
ATTR_INSTALLED, ATTR_LOGO, ATTR_WEBUI, ATTR_DEVICES, ATTR_PRIVILEGED,
|
ATTR_AUDIO_INPUT,
|
||||||
ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API,
|
ATTR_AUDIO_OUTPUT,
|
||||||
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, BOOT_AUTO, BOOT_MANUAL,
|
ATTR_AUTH_API,
|
||||||
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION,
|
ATTR_AUTO_UPDATE,
|
||||||
ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX,
|
ATTR_AVAILABLE,
|
||||||
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
ATTR_BLK_READ,
|
||||||
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
ATTR_BLK_WRITE,
|
||||||
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
|
ATTR_BOOT,
|
||||||
ATTR_HASSIO_ROLE, ATTR_MACHINE, ATTR_AVAILABLE, ATTR_AUTH_API,
|
ATTR_BUILD,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
|
ATTR_CHANGELOG,
|
||||||
|
ATTR_CPU_PERCENT,
|
||||||
|
ATTR_DESCRIPTON,
|
||||||
|
ATTR_DETACHED,
|
||||||
|
ATTR_DEVICES,
|
||||||
|
ATTR_DEVICETREE,
|
||||||
|
ATTR_DISCOVERY,
|
||||||
|
ATTR_DOCKER_API,
|
||||||
|
ATTR_FULL_ACCESS,
|
||||||
|
ATTR_GPIO,
|
||||||
|
ATTR_HASSIO_API,
|
||||||
|
ATTR_HASSIO_ROLE,
|
||||||
|
ATTR_HOMEASSISTANT,
|
||||||
|
ATTR_HOMEASSISTANT_API,
|
||||||
|
ATTR_HOST_DBUS,
|
||||||
|
ATTR_HOST_IPC,
|
||||||
|
ATTR_HOST_NETWORK,
|
||||||
|
ATTR_HOST_PID,
|
||||||
|
ATTR_ICON,
|
||||||
|
ATTR_INGRESS,
|
||||||
|
ATTR_INGRESS_ENTRY,
|
||||||
|
ATTR_INGRESS_PORT,
|
||||||
|
ATTR_INGRESS_URL,
|
||||||
|
ATTR_INSTALLED,
|
||||||
|
ATTR_IP_ADDRESS,
|
||||||
|
ATTR_KERNEL_MODULES,
|
||||||
|
ATTR_LAST_VERSION,
|
||||||
|
ATTR_LOGO,
|
||||||
|
ATTR_LONG_DESCRIPTION,
|
||||||
|
ATTR_MACHINE,
|
||||||
|
ATTR_MAINTAINER,
|
||||||
|
ATTR_MEMORY_LIMIT,
|
||||||
|
ATTR_MEMORY_USAGE,
|
||||||
|
ATTR_NAME,
|
||||||
|
ATTR_NETWORK,
|
||||||
|
ATTR_NETWORK_DESCRIPTION,
|
||||||
|
ATTR_NETWORK_RX,
|
||||||
|
ATTR_NETWORK_TX,
|
||||||
|
ATTR_OPTIONS,
|
||||||
|
ATTR_PRIVILEGED,
|
||||||
|
ATTR_PROTECTED,
|
||||||
|
ATTR_RATING,
|
||||||
|
ATTR_REPOSITORIES,
|
||||||
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SERVICES,
|
||||||
|
ATTR_SLUG,
|
||||||
|
ATTR_SOURCE,
|
||||||
|
ATTR_STATE,
|
||||||
|
ATTR_STDIN,
|
||||||
|
ATTR_URL,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WEBUI,
|
||||||
|
BOOT_AUTO,
|
||||||
|
BOOT_MANUAL,
|
||||||
|
CONTENT_TYPE_BINARY,
|
||||||
|
CONTENT_TYPE_PNG,
|
||||||
|
CONTENT_TYPE_TEXT,
|
||||||
|
REQUEST_FROM,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
|
from ..validate import ALSA_DEVICE, DOCKER_PORTS
|
||||||
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,7 +111,7 @@ SCHEMA_SECURITY = vol.Schema({
|
|||||||
class APIAddons(CoreSysAttributes):
|
class APIAddons(CoreSysAttributes):
|
||||||
"""Handle RESTful API for add-on functions."""
|
"""Handle RESTful API for add-on functions."""
|
||||||
|
|
||||||
def _extract_addon(self, request, check_installed=True):
|
def _extract_addon(self, request: web.Request, check_installed: bool = True) -> Addon:
|
||||||
"""Return addon, throw an exception it it doesn't exist."""
|
"""Return addon, throw an exception it it doesn't exist."""
|
||||||
addon_slug = request.match_info.get('addon')
|
addon_slug = request.match_info.get('addon')
|
||||||
|
|
||||||
@@ -68,7 +129,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return addon
|
return addon
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def list(self, request):
|
async def list(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return all add-ons or repositories."""
|
"""Return all add-ons or repositories."""
|
||||||
data_addons = []
|
data_addons = []
|
||||||
for addon in self.sys_addons.list_addons:
|
for addon in self.sys_addons.list_addons:
|
||||||
@@ -76,7 +137,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_NAME: addon.name,
|
ATTR_NAME: addon.name,
|
||||||
ATTR_SLUG: addon.slug,
|
ATTR_SLUG: addon.slug,
|
||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_DESCRIPTON: addon.description,
|
||||||
ATTR_VERSION: addon.last_version,
|
ATTR_VERSION: addon.latest_version,
|
||||||
ATTR_INSTALLED: addon.version_installed,
|
ATTR_INSTALLED: addon.version_installed,
|
||||||
ATTR_AVAILABLE: addon.available,
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
@@ -103,13 +164,12 @@ class APIAddons(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def reload(self, request):
|
async def reload(self, request: web.Request) -> None:
|
||||||
"""Reload all add-on data."""
|
"""Reload all add-on data."""
|
||||||
await asyncio.shield(self.sys_addons.reload())
|
await asyncio.shield(self.sys_addons.reload())
|
||||||
return True
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return add-on information."""
|
"""Return add-on information."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
|
|
||||||
@@ -121,7 +181,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_VERSION: addon.version_installed,
|
ATTR_VERSION: addon.version_installed,
|
||||||
ATTR_AUTO_UPDATE: addon.auto_update,
|
ATTR_AUTO_UPDATE: addon.auto_update,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_LAST_VERSION: addon.last_version,
|
ATTR_LAST_VERSION: addon.latest_version,
|
||||||
ATTR_STATE: await addon.state(),
|
ATTR_STATE: await addon.state(),
|
||||||
ATTR_PROTECTED: addon.protected,
|
ATTR_PROTECTED: addon.protected,
|
||||||
ATTR_RATING: rating_security(addon),
|
ATTR_RATING: rating_security(addon),
|
||||||
@@ -129,11 +189,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_OPTIONS: addon.options,
|
ATTR_OPTIONS: addon.options,
|
||||||
ATTR_ARCH: addon.supported_arch,
|
ATTR_ARCH: addon.supported_arch,
|
||||||
ATTR_MACHINE: addon.supported_machine,
|
ATTR_MACHINE: addon.supported_machine,
|
||||||
|
ATTR_HOMEASSISTANT: addon.homeassistant_version,
|
||||||
ATTR_URL: addon.url,
|
ATTR_URL: addon.url,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
ATTR_AVAILABLE: addon.available,
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_NETWORK: addon.ports,
|
ATTR_NETWORK: addon.ports,
|
||||||
|
ATTR_NETWORK_DESCRIPTION: addon.ports_description,
|
||||||
ATTR_HOST_NETWORK: addon.host_network,
|
ATTR_HOST_NETWORK: addon.host_network,
|
||||||
ATTR_HOST_PID: addon.host_pid,
|
ATTR_HOST_PID: addon.host_pid,
|
||||||
ATTR_HOST_IPC: addon.host_ipc,
|
ATTR_HOST_IPC: addon.host_ipc,
|
||||||
@@ -152,6 +214,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_AUTH_API: addon.access_auth_api,
|
ATTR_AUTH_API: addon.access_auth_api,
|
||||||
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_KERNEL_MODULES: addon.with_kernel_modules,
|
||||||
ATTR_DEVICETREE: addon.with_devicetree,
|
ATTR_DEVICETREE: addon.with_devicetree,
|
||||||
ATTR_DOCKER_API: addon.access_docker_api,
|
ATTR_DOCKER_API: addon.access_docker_api,
|
||||||
ATTR_AUDIO: addon.with_audio,
|
ATTR_AUDIO: addon.with_audio,
|
||||||
@@ -159,17 +222,21 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
||||||
ATTR_SERVICES: _pretty_services(addon),
|
ATTR_SERVICES: _pretty_services(addon),
|
||||||
ATTR_DISCOVERY: addon.discovery,
|
ATTR_DISCOVERY: addon.discovery,
|
||||||
|
ATTR_IP_ADDRESS: str(addon.ip_address),
|
||||||
|
ATTR_INGRESS: addon.with_ingress,
|
||||||
|
ATTR_INGRESS_ENTRY: addon.ingress_entry,
|
||||||
|
ATTR_INGRESS_URL: addon.ingress_url,
|
||||||
|
ATTR_INGRESS_PORT: addon.ingress_port,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request: web.Request) -> None:
|
||||||
"""Store user options for add-on."""
|
"""Store user options for add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
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),
|
||||||
})
|
})
|
||||||
|
|
||||||
body = await api_validate(addon_schema, request)
|
body = await api_validate(addon_schema, request)
|
||||||
|
|
||||||
if ATTR_OPTIONS in body:
|
if ATTR_OPTIONS in body:
|
||||||
@@ -186,10 +253,9 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||||
|
|
||||||
addon.save_data()
|
addon.save_data()
|
||||||
return True
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def security(self, request):
|
async def security(self, request: web.Request) -> None:
|
||||||
"""Store security options for add-on."""
|
"""Store security options for add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
body = await api_validate(SCHEMA_SECURITY, request)
|
body = await api_validate(SCHEMA_SECURITY, request)
|
||||||
@@ -199,17 +265,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon.protected = body[ATTR_PROTECTED]
|
addon.protected = body[ATTR_PROTECTED]
|
||||||
|
|
||||||
addon.save_data()
|
addon.save_data()
|
||||||
return True
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request):
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
stats = await addon.stats()
|
stats = await addon.stats()
|
||||||
|
|
||||||
if not stats:
|
|
||||||
raise APIError("No stats available")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
ATTR_MEMORY_USAGE: stats.memory_usage,
|
ATTR_MEMORY_USAGE: stats.memory_usage,
|
||||||
@@ -221,19 +283,19 @@ class APIAddons(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def install(self, request):
|
def install(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Install add-on."""
|
"""Install add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
return asyncio.shield(addon.install())
|
return asyncio.shield(addon.install())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def uninstall(self, request):
|
def uninstall(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Uninstall add-on."""
|
"""Uninstall add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.uninstall())
|
return asyncio.shield(addon.uninstall())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request):
|
def start(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Start add-on."""
|
"""Start add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
@@ -247,29 +309,29 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return asyncio.shield(addon.start())
|
return asyncio.shield(addon.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request):
|
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Stop add-on."""
|
"""Stop add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.stop())
|
return asyncio.shield(addon.stop())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def update(self, request):
|
def update(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Update add-on."""
|
"""Update add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
if addon.last_version == addon.version_installed:
|
if addon.latest_version == addon.version_installed:
|
||||||
raise APIError("No update available!")
|
raise APIError("No update available!")
|
||||||
|
|
||||||
return asyncio.shield(addon.update())
|
return asyncio.shield(addon.update())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart add-on."""
|
"""Restart add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.restart())
|
return asyncio.shield(addon.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def rebuild(self, request):
|
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Rebuild local build add-on."""
|
"""Rebuild local build add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
if not addon.need_build:
|
if not addon.need_build:
|
||||||
@@ -278,13 +340,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return asyncio.shield(addon.rebuild())
|
return asyncio.shield(addon.rebuild())
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return logs from add-on."""
|
"""Return logs from add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return addon.logs()
|
return addon.logs()
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_PNG)
|
@api_process_raw(CONTENT_TYPE_PNG)
|
||||||
async def icon(self, request):
|
async def icon(self, request: web.Request) -> bytes:
|
||||||
"""Return icon from add-on."""
|
"""Return icon from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_icon:
|
if not addon.with_icon:
|
||||||
@@ -294,7 +356,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return png.read()
|
return png.read()
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_PNG)
|
@api_process_raw(CONTENT_TYPE_PNG)
|
||||||
async def logo(self, request):
|
async def logo(self, request: web.Request) -> bytes:
|
||||||
"""Return logo from add-on."""
|
"""Return logo from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_logo:
|
if not addon.with_logo:
|
||||||
@@ -304,7 +366,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return png.read()
|
return png.read()
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_TEXT)
|
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||||
async def changelog(self, request):
|
async def changelog(self, request: web.Request) -> str:
|
||||||
"""Return changelog from add-on."""
|
"""Return changelog from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_changelog:
|
if not addon.with_changelog:
|
||||||
@@ -314,17 +376,17 @@ class APIAddons(CoreSysAttributes):
|
|||||||
return changelog.read()
|
return changelog.read()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stdin(self, request):
|
async def stdin(self, request: web.Request) -> None:
|
||||||
"""Write to stdin of add-on."""
|
"""Write to stdin of add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
if not addon.with_stdin:
|
if not addon.with_stdin:
|
||||||
raise APIError("STDIN not supported by add-on")
|
raise APIError("STDIN not supported by add-on")
|
||||||
|
|
||||||
data = await request.read()
|
data = await request.read()
|
||||||
return await asyncio.shield(addon.write_stdin(data))
|
await asyncio.shield(addon.write_stdin(data))
|
||||||
|
|
||||||
|
|
||||||
def _pretty_devices(addon):
|
def _pretty_devices(addon: Addon) -> List[str]:
|
||||||
"""Return a simplified device list."""
|
"""Return a simplified device list."""
|
||||||
dev_list = addon.devices
|
dev_list = addon.devices
|
||||||
if not dev_list:
|
if not dev_list:
|
||||||
@@ -332,7 +394,7 @@ def _pretty_devices(addon):
|
|||||||
return [row.split(':')[0] for row in dev_list]
|
return [row.split(':')[0] for row in dev_list]
|
||||||
|
|
||||||
|
|
||||||
def _pretty_services(addon):
|
def _pretty_services(addon: Addon) -> List[str]:
|
||||||
"""Return a simplified services role list."""
|
"""Return a simplified services role list."""
|
||||||
services = []
|
services = []
|
||||||
for name, access in addon.services_role.items():
|
for name, access in addon.services_role.items():
|
||||||
|
@@ -3,17 +3,24 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDON, ATTR_UUID, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_SERVICE,
|
ATTR_ADDON,
|
||||||
REQUEST_FROM)
|
ATTR_UUID,
|
||||||
|
ATTR_CONFIG,
|
||||||
|
ATTR_DISCOVERY,
|
||||||
|
ATTR_SERVICE,
|
||||||
|
REQUEST_FROM,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden
|
||||||
from ..validate import SERVICE_ALL
|
from ..discovery.validate import valid_discovery_service
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema({
|
SCHEMA_DISCOVERY = vol.Schema(
|
||||||
vol.Required(ATTR_SERVICE): SERVICE_ALL,
|
{
|
||||||
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
|
vol.Required(ATTR_SERVICE): valid_discovery_service,
|
||||||
})
|
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIDiscovery(CoreSysAttributes):
|
class APIDiscovery(CoreSysAttributes):
|
||||||
@@ -21,7 +28,7 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
|
|
||||||
def _extract_message(self, request):
|
def _extract_message(self, request):
|
||||||
"""Extract discovery message from URL."""
|
"""Extract discovery message from URL."""
|
||||||
message = self.sys_discovery.get(request.match_info.get('uuid'))
|
message = self.sys_discovery.get(request.match_info.get("uuid"))
|
||||||
if not message:
|
if not message:
|
||||||
raise APIError("Discovery message not found")
|
raise APIError("Discovery message not found")
|
||||||
return message
|
return message
|
||||||
@@ -38,12 +45,14 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
|
|
||||||
discovery = []
|
discovery = []
|
||||||
for message in self.sys_discovery.list_messages:
|
for message in self.sys_discovery.list_messages:
|
||||||
discovery.append({
|
discovery.append(
|
||||||
ATTR_ADDON: message.addon,
|
{
|
||||||
ATTR_SERVICE: message.service,
|
ATTR_ADDON: message.addon,
|
||||||
ATTR_UUID: message.uuid,
|
ATTR_SERVICE: message.service,
|
||||||
ATTR_CONFIG: message.config,
|
ATTR_UUID: message.uuid,
|
||||||
})
|
ATTR_CONFIG: message.config,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {ATTR_DISCOVERY: discovery}
|
return {ATTR_DISCOVERY: discovery}
|
||||||
|
|
||||||
|
@@ -1,27 +1,31 @@
|
|||||||
"""Init file for Hass.io HassOS RESTful API."""
|
"""Init file for Hass.io HassOS RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
from .utils import api_process, api_validate
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_VERSION, ATTR_BOARD, ATTR_VERSION_LATEST, ATTR_VERSION_CLI,
|
ATTR_BOARD,
|
||||||
ATTR_VERSION_CLI_LATEST)
|
ATTR_VERSION,
|
||||||
|
ATTR_VERSION_CLI,
|
||||||
|
ATTR_VERSION_CLI_LATEST,
|
||||||
|
ATTR_VERSION_LATEST,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class APIHassOS(CoreSysAttributes):
|
class APIHassOS(CoreSysAttributes):
|
||||||
"""Handle RESTful API for HassOS functions."""
|
"""Handle RESTful API for HassOS functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return HassOS information."""
|
"""Return HassOS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_hassos.version,
|
ATTR_VERSION: self.sys_hassos.version,
|
||||||
@@ -32,7 +36,7 @@ class APIHassOS(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update HassOS."""
|
"""Update HassOS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_hassos.version_latest)
|
version = body.get(ATTR_VERSION, self.sys_hassos.version_latest)
|
||||||
@@ -40,7 +44,7 @@ class APIHassOS(CoreSysAttributes):
|
|||||||
await asyncio.shield(self.sys_hassos.update(version))
|
await asyncio.shield(self.sys_hassos.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update_cli(self, request):
|
async def update_cli(self, request: web.Request) -> None:
|
||||||
"""Update HassOS CLI."""
|
"""Update HassOS CLI."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_hassos.version_cli_latest)
|
version = body.get(ATTR_VERSION, self.sys_hassos.version_cli_latest)
|
||||||
@@ -48,6 +52,6 @@ class APIHassOS(CoreSysAttributes):
|
|||||||
await asyncio.shield(self.sys_hassos.update_cli(version))
|
await asyncio.shield(self.sys_hassos.update_cli(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def config_sync(self, request):
|
def config_sync(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Trigger config reload on HassOS."""
|
"""Trigger config reload on HassOS."""
|
||||||
return asyncio.shield(self.sys_hassos.config_sync())
|
return asyncio.shield(self.sys_hassos.config_sync())
|
||||||
|
@@ -1,54 +1,72 @@
|
|||||||
"""Init file for Hass.io Home Assistant RESTful API."""
|
"""Init file for Hass.io Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Coroutine, Dict, Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_IMAGE, ATTR_CUSTOM, ATTR_BOOT,
|
ATTR_ARCH,
|
||||||
ATTR_PORT, ATTR_PASSWORD, ATTR_SSL, ATTR_WATCHDOG, ATTR_CPU_PERCENT,
|
ATTR_BLK_READ,
|
||||||
ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX,
|
ATTR_BLK_WRITE,
|
||||||
ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_WAIT_BOOT, ATTR_MACHINE,
|
ATTR_BOOT,
|
||||||
ATTR_REFRESH_TOKEN, CONTENT_TYPE_BINARY)
|
ATTR_CPU_PERCENT,
|
||||||
|
ATTR_CUSTOM,
|
||||||
|
ATTR_IMAGE,
|
||||||
|
ATTR_LAST_VERSION,
|
||||||
|
ATTR_MACHINE,
|
||||||
|
ATTR_MEMORY_LIMIT,
|
||||||
|
ATTR_MEMORY_USAGE,
|
||||||
|
ATTR_NETWORK_RX,
|
||||||
|
ATTR_NETWORK_TX,
|
||||||
|
ATTR_PASSWORD,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WAIT_BOOT,
|
||||||
|
ATTR_WATCHDOG,
|
||||||
|
ATTR_IP_ADDRESS,
|
||||||
|
CONTENT_TYPE_BINARY,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import NETWORK_PORT, DOCKER_IMAGE
|
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
|
from ..validate import DOCKER_IMAGE, NETWORK_PORT
|
||||||
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema({
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
{
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
||||||
vol.Maybe(vol.Coerce(str)),
|
vol.Inclusive(ATTR_IMAGE, "custom_hass"): 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.Maybe(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.All(vol.Coerce(int), vol.Range(min=60)),
|
||||||
vol.Optional(ATTR_WAIT_BOOT):
|
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
}
|
||||||
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
)
|
||||||
})
|
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class APIHomeAssistant(CoreSysAttributes):
|
class APIHomeAssistant(CoreSysAttributes):
|
||||||
"""Handle RESTful API for Home Assistant functions."""
|
"""Handle RESTful API for Home Assistant functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return host information."""
|
"""Return host information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_homeassistant.version,
|
ATTR_VERSION: self.sys_homeassistant.version,
|
||||||
ATTR_LAST_VERSION: self.sys_homeassistant.last_version,
|
ATTR_LAST_VERSION: self.sys_homeassistant.latest_version,
|
||||||
ATTR_MACHINE: self.sys_homeassistant.machine,
|
ATTR_MACHINE: self.sys_homeassistant.machine,
|
||||||
|
ATTR_IP_ADDRESS: str(self.sys_homeassistant.ip_address),
|
||||||
|
ATTR_ARCH: self.sys_homeassistant.arch,
|
||||||
ATTR_IMAGE: self.sys_homeassistant.image,
|
ATTR_IMAGE: self.sys_homeassistant.image,
|
||||||
ATTR_CUSTOM: self.sys_homeassistant.is_custom_image,
|
ATTR_CUSTOM: self.sys_homeassistant.is_custom_image,
|
||||||
ATTR_BOOT: self.sys_homeassistant.boot,
|
ATTR_BOOT: self.sys_homeassistant.boot,
|
||||||
@@ -59,13 +77,13 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request: web.Request) -> None:
|
||||||
"""Set Home Assistant options."""
|
"""Set Home Assistant options."""
|
||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
|
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
|
||||||
self.sys_homeassistant.image = body[ATTR_IMAGE]
|
self.sys_homeassistant.image = body[ATTR_IMAGE]
|
||||||
self.sys_homeassistant.last_version = body[ATTR_LAST_VERSION]
|
self.sys_homeassistant.latest_version = body[ATTR_LAST_VERSION]
|
||||||
|
|
||||||
if ATTR_BOOT in body:
|
if ATTR_BOOT in body:
|
||||||
self.sys_homeassistant.boot = body[ATTR_BOOT]
|
self.sys_homeassistant.boot = body[ATTR_BOOT]
|
||||||
@@ -75,6 +93,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
|
|
||||||
if ATTR_PASSWORD in body:
|
if ATTR_PASSWORD in body:
|
||||||
self.sys_homeassistant.api_password = body[ATTR_PASSWORD]
|
self.sys_homeassistant.api_password = body[ATTR_PASSWORD]
|
||||||
|
self.sys_homeassistant.refresh_token = None
|
||||||
|
|
||||||
if ATTR_SSL in body:
|
if ATTR_SSL in body:
|
||||||
self.sys_homeassistant.api_ssl = body[ATTR_SSL]
|
self.sys_homeassistant.api_ssl = body[ATTR_SSL]
|
||||||
@@ -91,7 +110,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
self.sys_homeassistant.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request):
|
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_homeassistant.stats()
|
stats = await self.sys_homeassistant.stats()
|
||||||
if not stats:
|
if not stats:
|
||||||
@@ -108,35 +127,40 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update Home Assistant."""
|
"""Update Home Assistant."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
|
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
||||||
|
|
||||||
await asyncio.shield(self.sys_homeassistant.update(version))
|
await asyncio.shield(self.sys_homeassistant.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request):
|
def stop(self, request: web.Request) -> Coroutine:
|
||||||
"""Stop Home Assistant."""
|
"""Stop Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.stop())
|
return asyncio.shield(self.sys_homeassistant.stop())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request):
|
def start(self, request: web.Request) -> Coroutine:
|
||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.start())
|
return asyncio.shield(self.sys_homeassistant.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request: web.Request) -> Coroutine:
|
||||||
"""Restart Home Assistant."""
|
"""Restart Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.restart())
|
return asyncio.shield(self.sys_homeassistant.restart())
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def rebuild(self, request: web.Request) -> Coroutine:
|
||||||
|
"""Rebuild Home Assistant."""
|
||||||
|
return asyncio.shield(self.sys_homeassistant.rebuild())
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request: web.Request) -> Coroutine:
|
||||||
"""Return Home Assistant Docker logs."""
|
"""Return Home Assistant Docker logs."""
|
||||||
return self.sys_homeassistant.logs()
|
return self.sys_homeassistant.logs()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def check(self, request):
|
async def check(self, request: web.Request) -> None:
|
||||||
"""Check configuration of Home Assistant."""
|
"""Check configuration of Home Assistant."""
|
||||||
result = await self.sys_homeassistant.check_config()
|
result = await self.sys_homeassistant.check_config()
|
||||||
if not result.valid:
|
if not result.valid:
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
"""Init file for Hass.io info RESTful API."""
|
"""Init file for Hass.io info RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .utils import api_process
|
from ..const import (ATTR_ARCH, ATTR_CHANNEL, ATTR_HASSOS, ATTR_HOMEASSISTANT,
|
||||||
from ..const import (
|
ATTR_HOSTNAME, ATTR_MACHINE, ATTR_SUPERVISOR,
|
||||||
ATTR_HOMEASSISTANT, ATTR_SUPERVISOR, ATTR_MACHINE, ATTR_ARCH, ATTR_HASSOS,
|
ATTR_SUPPORTED_ARCH)
|
||||||
ATTR_CHANNEL, ATTR_HOSTNAME)
|
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from .utils import api_process
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ class APIInfo(CoreSysAttributes):
|
|||||||
ATTR_HASSOS: self.sys_hassos.version,
|
ATTR_HASSOS: self.sys_hassos.version,
|
||||||
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
||||||
ATTR_MACHINE: self.sys_machine,
|
ATTR_MACHINE: self.sys_machine,
|
||||||
ATTR_ARCH: self.sys_arch,
|
ATTR_ARCH: self.sys_arch.default,
|
||||||
|
ATTR_SUPPORTED_ARCH: self.sys_arch.supported,
|
||||||
ATTR_CHANNEL: self.sys_updater.channel,
|
ATTR_CHANNEL: self.sys_updater.channel,
|
||||||
}
|
}
|
||||||
|
234
hassio/api/ingress.py
Normal file
234
hassio/api/ingress.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
"""Hass.io Add-on ingress service."""
|
||||||
|
import asyncio
|
||||||
|
from ipaddress import ip_address
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp import hdrs, web
|
||||||
|
from aiohttp.web_exceptions import (
|
||||||
|
HTTPBadGateway,
|
||||||
|
HTTPServiceUnavailable,
|
||||||
|
HTTPUnauthorized,
|
||||||
|
)
|
||||||
|
from multidict import CIMultiDict, istr
|
||||||
|
|
||||||
|
from ..addons.addon import Addon
|
||||||
|
from ..const import ATTR_SESSION, HEADER_TOKEN, REQUEST_FROM, COOKIE_INGRESS
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
from .utils import api_process
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class APIIngress(CoreSysAttributes):
|
||||||
|
"""Ingress view to handle add-on webui routing."""
|
||||||
|
|
||||||
|
def _extract_addon(self, request: web.Request) -> Addon:
|
||||||
|
"""Return addon, throw an exception it it doesn't exist."""
|
||||||
|
token = request.match_info.get("token")
|
||||||
|
|
||||||
|
# Find correct add-on
|
||||||
|
addon = self.sys_ingress.get(token)
|
||||||
|
if not addon:
|
||||||
|
_LOGGER.warning("Ingress for %s not available", token)
|
||||||
|
raise HTTPServiceUnavailable()
|
||||||
|
|
||||||
|
return addon
|
||||||
|
|
||||||
|
def _check_ha_access(self, request: web.Request) -> None:
|
||||||
|
if request[REQUEST_FROM] != self.sys_homeassistant:
|
||||||
|
_LOGGER.warning("Ingress is only available behind Home Assistant")
|
||||||
|
raise HTTPUnauthorized()
|
||||||
|
|
||||||
|
def _create_url(self, addon: Addon, path: str) -> str:
|
||||||
|
"""Create URL to container."""
|
||||||
|
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def create_session(self, request: web.Request) -> Dict[str, Any]:
|
||||||
|
"""Create a new session."""
|
||||||
|
self._check_ha_access(request)
|
||||||
|
|
||||||
|
session = self.sys_ingress.create_session()
|
||||||
|
return {ATTR_SESSION: session}
|
||||||
|
|
||||||
|
async def handler(
|
||||||
|
self, request: web.Request
|
||||||
|
) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
|
||||||
|
"""Route data to Hass.io ingress service."""
|
||||||
|
self._check_ha_access(request)
|
||||||
|
|
||||||
|
# Check Ingress Session
|
||||||
|
session = request.cookies.get(COOKIE_INGRESS)
|
||||||
|
if not self.sys_ingress.validate_session(session):
|
||||||
|
_LOGGER.warning("No valid ingress session %s", session)
|
||||||
|
raise HTTPUnauthorized()
|
||||||
|
|
||||||
|
# Process requests
|
||||||
|
addon = self._extract_addon(request)
|
||||||
|
path = request.match_info.get("path")
|
||||||
|
try:
|
||||||
|
# Websocket
|
||||||
|
if _is_websocket(request):
|
||||||
|
return await self._handle_websocket(request, addon, path)
|
||||||
|
|
||||||
|
# Request
|
||||||
|
return await self._handle_request(request, addon, path)
|
||||||
|
|
||||||
|
except aiohttp.ClientError as err:
|
||||||
|
_LOGGER.error("Ingress error: %s", err)
|
||||||
|
|
||||||
|
raise HTTPBadGateway() from None
|
||||||
|
|
||||||
|
async def _handle_websocket(
|
||||||
|
self, request: web.Request, addon: Addon, path: str
|
||||||
|
) -> web.WebSocketResponse:
|
||||||
|
"""Ingress route for websocket."""
|
||||||
|
if hdrs.SEC_WEBSOCKET_PROTOCOL in request.headers:
|
||||||
|
req_protocols = [
|
||||||
|
str(proto.strip())
|
||||||
|
for proto in request.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
req_protocols = ()
|
||||||
|
|
||||||
|
ws_server = web.WebSocketResponse(
|
||||||
|
protocols=req_protocols, autoclose=False, autoping=False
|
||||||
|
)
|
||||||
|
await ws_server.prepare(request)
|
||||||
|
|
||||||
|
# Preparing
|
||||||
|
url = self._create_url(addon, path)
|
||||||
|
source_header = _init_header(request, addon)
|
||||||
|
|
||||||
|
# Support GET query
|
||||||
|
if request.query_string:
|
||||||
|
url = "{}?{}".format(url, request.query_string)
|
||||||
|
|
||||||
|
# Start proxy
|
||||||
|
async with self.sys_websession.ws_connect(
|
||||||
|
url,
|
||||||
|
headers=source_header,
|
||||||
|
protocols=req_protocols,
|
||||||
|
autoclose=False,
|
||||||
|
autoping=False,
|
||||||
|
) as ws_client:
|
||||||
|
# Proxy requests
|
||||||
|
await asyncio.wait(
|
||||||
|
[
|
||||||
|
_websocket_forward(ws_server, ws_client),
|
||||||
|
_websocket_forward(ws_client, ws_server),
|
||||||
|
],
|
||||||
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ws_server
|
||||||
|
|
||||||
|
async def _handle_request(
|
||||||
|
self, request: web.Request, addon: Addon, path: str
|
||||||
|
) -> Union[web.Response, web.StreamResponse]:
|
||||||
|
"""Ingress route for request."""
|
||||||
|
url = self._create_url(addon, path)
|
||||||
|
data = await request.read()
|
||||||
|
source_header = _init_header(request, addon)
|
||||||
|
|
||||||
|
async with self.sys_websession.request(
|
||||||
|
request.method, url, headers=source_header, params=request.query, data=data
|
||||||
|
) as result:
|
||||||
|
headers = _response_header(result)
|
||||||
|
|
||||||
|
# Simple request
|
||||||
|
if (
|
||||||
|
hdrs.CONTENT_LENGTH in result.headers
|
||||||
|
and int(result.headers.get(hdrs.CONTENT_LENGTH, 0)) < 4_194_000
|
||||||
|
):
|
||||||
|
# Return Response
|
||||||
|
body = await result.read()
|
||||||
|
return web.Response(
|
||||||
|
headers=headers,
|
||||||
|
status=result.status,
|
||||||
|
content_type=result.content_type,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stream response
|
||||||
|
response = web.StreamResponse(status=result.status, headers=headers)
|
||||||
|
response.content_type = result.content_type
|
||||||
|
|
||||||
|
try:
|
||||||
|
await response.prepare(request)
|
||||||
|
async for data in result.content.iter_chunked(4096):
|
||||||
|
await response.write(data)
|
||||||
|
|
||||||
|
except (aiohttp.ClientError, aiohttp.ClientPayloadError) as err:
|
||||||
|
_LOGGER.error("Stream error with %s: %s", url, err)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def _init_header(
|
||||||
|
request: web.Request, addon: str
|
||||||
|
) -> Union[CIMultiDict, Dict[str, str]]:
|
||||||
|
"""Create initial header."""
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
# filter flags
|
||||||
|
for name, value in request.headers.items():
|
||||||
|
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_ENCODING, istr(HEADER_TOKEN)):
|
||||||
|
continue
|
||||||
|
headers[name] = value
|
||||||
|
|
||||||
|
# Update X-Forwarded-For
|
||||||
|
forward_for = request.headers.get(hdrs.X_FORWARDED_FOR)
|
||||||
|
connected_ip = ip_address(request.transport.get_extra_info("peername")[0])
|
||||||
|
headers[hdrs.X_FORWARDED_FOR] = f"{forward_for}, {connected_ip!s}"
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]:
|
||||||
|
"""Create response header."""
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
for name, value in response.headers.items():
|
||||||
|
if name in (
|
||||||
|
hdrs.TRANSFER_ENCODING,
|
||||||
|
hdrs.CONTENT_LENGTH,
|
||||||
|
hdrs.CONTENT_TYPE,
|
||||||
|
hdrs.CONTENT_ENCODING,
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
headers[name] = value
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def _is_websocket(request: web.Request) -> bool:
|
||||||
|
"""Return True if request is a websocket."""
|
||||||
|
headers = request.headers
|
||||||
|
|
||||||
|
if (
|
||||||
|
headers.get(hdrs.CONNECTION) == "Upgrade"
|
||||||
|
and headers.get(hdrs.UPGRADE) == "websocket"
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def _websocket_forward(ws_from, ws_to):
|
||||||
|
"""Handle websocket message directly."""
|
||||||
|
try:
|
||||||
|
async for msg in ws_from:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
await ws_to.send_str(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
|
await ws_to.send_bytes(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.PING:
|
||||||
|
await ws_to.ping()
|
||||||
|
elif msg.type == aiohttp.WSMsgType.PONG:
|
||||||
|
await ws_to.pong()
|
||||||
|
elif ws_to.closed:
|
||||||
|
await ws_to.close(code=ws_to.close_code, message=msg.extra)
|
||||||
|
except RuntimeError:
|
||||||
|
_LOGGER.warning("Ingress Websocket runtime error")
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
1
hassio/api/panel/chunk.183d719f6ced86001f8e.js
Normal file
1
hassio/api/panel/chunk.183d719f6ced86001f8e.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.183d719f6ced86001f8e.js.gz
Normal file
BIN
hassio/api/panel/chunk.183d719f6ced86001f8e.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.3a63ad36bccf4ea567fa.js
Normal file
1
hassio/api/panel/chunk.3a63ad36bccf4ea567fa.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{102:function(n,r,t){"use strict";t.r(r),t.d(r,"marked",function(){return a}),t.d(r,"filterXSS",function(){return c});var e=t(121),i=t.n(e),o=t(123),u=t.n(o),a=i.a,c=u.a}}]);
|
BIN
hassio/api/panel/chunk.3a63ad36bccf4ea567fa.js.gz
Normal file
BIN
hassio/api/panel/chunk.3a63ad36bccf4ea567fa.js.gz
Normal file
Binary file not shown.
3
hassio/api/panel/chunk.510634470d399e194ace.js
Normal file
3
hassio/api/panel/chunk.510634470d399e194ace.js
Normal file
File diff suppressed because one or more lines are too long
10
hassio/api/panel/chunk.510634470d399e194ace.js.LICENSE
Normal file
10
hassio/api/panel/chunk.510634470d399e194ace.js.LICENSE
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.510634470d399e194ace.js.gz
Normal file
BIN
hassio/api/panel/chunk.510634470d399e194ace.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.510634470d399e194ace.js.map
Normal file
1
hassio/api/panel/chunk.510634470d399e194ace.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.510634470d399e194ace.js","sourceRoot":""}
|
3
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js
Normal file
3
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js
Normal file
File diff suppressed because one or more lines are too long
21
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.LICENSE
Normal file
21
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.gz
Normal file
BIN
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.map
Normal file
1
hassio/api/panel/chunk.564a2f7b1c38ddaa4ce0.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.564a2f7b1c38ddaa4ce0.js","sourceRoot":""}
|
1
hassio/api/panel/chunk.5d31a1778f717ac8b063.js
Normal file
1
hassio/api/panel/chunk.5d31a1778f717ac8b063.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.5d31a1778f717ac8b063.js.gz
Normal file
BIN
hassio/api/panel/chunk.5d31a1778f717ac8b063.js.gz
Normal file
Binary file not shown.
3
hassio/api/panel/chunk.659084fef4e3b7b66a76.js
Normal file
3
hassio/api/panel/chunk.659084fef4e3b7b66a76.js
Normal file
File diff suppressed because one or more lines are too long
32
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.LICENSE
Normal file
32
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.LICENSE
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview
|
||||||
|
* @suppress {checkPrototypalTypes}
|
||||||
|
* @license Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at
|
||||||
|
* http://polymer.github.io/LICENSE.txt The complete set of authors may be found
|
||||||
|
* at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
|
||||||
|
* be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
|
||||||
|
* Google as part of the polymer project is also subject to an additional IP
|
||||||
|
* rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.gz
Normal file
BIN
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.map
Normal file
1
hassio/api/panel/chunk.659084fef4e3b7b66a76.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.659084fef4e3b7b66a76.js","sourceRoot":""}
|
3
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js
Normal file
3
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js
Normal file
File diff suppressed because one or more lines are too long
31
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.LICENSE
Normal file
31
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.LICENSE
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.gz
Normal file
BIN
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.map
Normal file
1
hassio/api/panel/chunk.6e9c87e51920a9c354e5.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.6e9c87e51920a9c354e5.js","sourceRoot":""}
|
1
hassio/api/panel/chunk.739b67c99ab56cdbd75d.js
Normal file
1
hassio/api/panel/chunk.739b67c99ab56cdbd75d.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.739b67c99ab56cdbd75d.js.gz
Normal file
BIN
hassio/api/panel/chunk.739b67c99ab56cdbd75d.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{100:function(n,r,t){"use strict";t.r(r),t.d(r,"marked",function(){return a}),t.d(r,"filterXSS",function(){return c});var e=t(89),i=t.n(e),o=t(91),u=t.n(o),a=i.a,c=u.a}}]);
|
|
||||||
//# sourceMappingURL=chunk.9e3883f96f68b3ce89f5.js.map
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack:///../src/resources/load_markdown.js"],"names":["__webpack_require__","r","__webpack_exports__","d","marked","filterXSS","marked__WEBPACK_IMPORTED_MODULE_0__","marked__WEBPACK_IMPORTED_MODULE_0___default","n","xss__WEBPACK_IMPORTED_MODULE_1__","xss__WEBPACK_IMPORTED_MODULE_1___default","marked_","filterXSS_"],"mappings":"0FAAAA,EAAAC,EAAAC,GAAAF,EAAAG,EAAAD,EAAA,2BAAAE,IAAAJ,EAAAG,EAAAD,EAAA,8BAAAG,IAAA,IAAAC,EAAAN,EAAA,IAAAO,EAAAP,EAAAQ,EAAAF,GAAAG,EAAAT,EAAA,IAAAU,EAAAV,EAAAQ,EAAAC,GAGaL,EAASO,IACTN,EAAYO","file":"chunk.9e3883f96f68b3ce89f5.js","sourcesContent":["import marked_ from \"marked\";\nimport filterXSS_ from \"xss\";\n\nexport const marked = marked_;\nexport const filterXSS = filterXSS_;\n"],"sourceRoot":""}
|
|
1
hassio/api/panel/chunk.a571dfa106202cc57af6.js
Normal file
1
hassio/api/panel/chunk.a571dfa106202cc57af6.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.a571dfa106202cc57af6.js.gz
Normal file
BIN
hassio/api/panel/chunk.a571dfa106202cc57af6.js.gz
Normal file
Binary file not shown.
3
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js
Normal file
3
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js
Normal file
File diff suppressed because one or more lines are too long
180
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.LICENSE
Normal file
180
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.LICENSE
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at
|
||||||
|
* http://polymer.github.io/LICENSE.txt
|
||||||
|
* The complete set of authors may be found at
|
||||||
|
* http://polymer.github.io/AUTHORS.txt
|
||||||
|
* The complete set of contributors may be found at
|
||||||
|
* http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
* Code distributed by Google as part of the polymer project is also
|
||||||
|
* subject to an additional IP rights grant found at
|
||||||
|
* http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright 2018 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||||
|
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||||
|
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
See the Apache Version 2.0 License for specific language governing permissions
|
||||||
|
and limitations under the License.
|
||||||
|
***************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2018 Google Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at
|
||||||
|
* http://polymer.github.io/LICENSE.txt
|
||||||
|
* The complete set of authors may be found at
|
||||||
|
* http://polymer.github.io/AUTHORS.txt
|
||||||
|
* The complete set of contributors may be found at
|
||||||
|
* http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
* Code distributed by Google as part of the polymer project is also
|
||||||
|
* subject to an additional IP rights grant found at
|
||||||
|
* http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.gz
Normal file
BIN
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.map
Normal file
1
hassio/api/panel/chunk.a7e5fb452cd1b3a5faef.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.a7e5fb452cd1b3a5faef.js","sourceRoot":""}
|
1
hassio/api/panel/chunk.b3340b3df270d20af4a1.js
Normal file
1
hassio/api/panel/chunk.b3340b3df270d20af4a1.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.b3340b3df270d20af4a1.js.gz
Normal file
BIN
hassio/api/panel/chunk.b3340b3df270d20af4a1.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,820 +0,0 @@
|
|||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
3
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js
Normal file
3
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js
Normal file
File diff suppressed because one or more lines are too long
10
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.LICENSE
Normal file
10
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.LICENSE
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.gz
Normal file
BIN
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.map
Normal file
1
hassio/api/panel/chunk.f15d7f41c0d302cbbc7a.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.f15d7f41c0d302cbbc7a.js","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
@@ -1,471 +0,0 @@
|
|||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
1
hassio/api/panel/chunk.feb68d852b3f6d67d970.js
Normal file
1
hassio/api/panel/chunk.feb68d852b3f6d67d970.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.feb68d852b3f6d67d970.js.gz
Normal file
BIN
hassio/api/panel/chunk.feb68d852b3f6d67d970.js.gz
Normal file
Binary file not shown.
@@ -1,2 +1 @@
|
|||||||
!function(e){function n(n){for(var t,o,i=n[0],u=n[1],c=0,f=[];c<i.length;c++)o=i[c],r[o]&&f.push(r[o][0]),r[o]=0;for(t in u)Object.prototype.hasOwnProperty.call(u,t)&&(e[t]=u[t]);for(a&&a(n);f.length;)f.shift()()}var t={},r={1:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var i=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=i);var u,c=document.getElementsByTagName("head")[0],a=document.createElement("script");a.charset="utf-8",a.timeout=120,o.nc&&a.setAttribute("nonce",o.nc),a.src=function(e){return o.p+"chunk."+{0:"f32f3c841cc3e1d081f7",2:"8c049a124b9397e54c16",3:"d0eb7b86b775838caf5e",4:"9e3883f96f68b3ce89f5",5:"0cb8b788b03dcc48da14",6:"c1ac97370d72bce0a835",7:"0853908528652fbc5d4f"}[e]+".js"}(e),u=function(n){a.onerror=a.onload=null,clearTimeout(f);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),i=n&&n.target&&n.target.src,u=new Error("Loading chunk "+e+" failed.\n("+o+": "+i+")");u.type=o,u.request=i,t[1](u)}r[e]=void 0}};var f=setTimeout(function(){u({type:"timeout",target:a})},12e4);a.onerror=a.onload=u,c.appendChild(a)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],u=i.push.bind(i);i.push=n,i=i.slice();for(var c=0;c<i.length;c++)n(i[c]);var a=u;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){Promise.all([t.e(0),t.e(2)]).then(t.bind(null,2)),Promise.all([t.e(0),t.e(6),t.e(3)]).then(t.bind(null,1))})}]);
|
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,u=[];c<a.length;c++)o=a[c],r[o]&&u.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(f&&f(n);u.length;)u.shift()()}var t={},r={4:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"564a2f7b1c38ddaa4ce0",1:"659084fef4e3b7b66a76",2:"510634470d399e194ace",3:"f15d7f41c0d302cbbc7a",5:"5d31a1778f717ac8b063",6:"feb68d852b3f6d67d970",7:"3a63ad36bccf4ea567fa",8:"a571dfa106202cc57af6",9:"a7e5fb452cd1b3a5faef",10:"b3340b3df270d20af4a1",11:"6e9c87e51920a9c354e5",12:"183d719f6ced86001f8e",13:"739b67c99ab56cdbd75d"}[e]+".js"}(e),i=function(n){c.onerror=c.onload=null,clearTimeout(f);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src,i=new Error("Loading chunk "+e+" failed.\n("+o+": "+a+")");i.type=o,i.request=a,t[1](i)}r[e]=void 0}};var f=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var c=0;c<a.length;c++)n(a[c]);var f=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){Promise.all([t.e(1),t.e(5)]).then(t.bind(null,2)),Promise.all([t.e(1),t.e(9),t.e(6)]).then(t.bind(null,1))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
|
||||||
//# sourceMappingURL=entrypoint.js.map
|
|
Binary file not shown.
@@ -35,7 +35,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
elif not addon.access_homeassistant_api:
|
elif not addon.access_homeassistant_api:
|
||||||
_LOGGER.warning("Not permitted API access: %s", addon.slug)
|
_LOGGER.warning("Not permitted API access: %s", addon.slug)
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
_LOGGER.debug("%s access from %s", request.path, addon.slug)
|
||||||
return
|
return
|
||||||
|
|
||||||
raise HTTPUnauthorized()
|
raise HTTPUnauthorized()
|
||||||
@@ -144,8 +144,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
return client
|
return client
|
||||||
|
|
||||||
# Renew the Token is invalid
|
# Renew the Token is invalid
|
||||||
if (data.get('type') == 'invalid_auth' and
|
if data.get('type') == 'invalid_auth' and self.sys_homeassistant.refresh_token:
|
||||||
self.sys_homeassistant.refresh_token):
|
|
||||||
self.sys_homeassistant.access_token = None
|
self.sys_homeassistant.access_token = None
|
||||||
return await self._websocket_client()
|
return await self._websocket_client()
|
||||||
|
|
||||||
@@ -175,8 +174,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
|
|
||||||
# Check API access
|
# Check API access
|
||||||
response = await server.receive_json()
|
response = await server.receive_json()
|
||||||
hassio_token = (response.get('api_password') or
|
hassio_token = response.get('api_password') or response.get('access_token')
|
||||||
response.get('access_token'))
|
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
|
|
||||||
if not addon or not addon.access_homeassistant_api:
|
if not addon or not addon.access_homeassistant_api:
|
||||||
|
@@ -6,12 +6,19 @@ from aiohttp.web import middleware
|
|||||||
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
|
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
HEADER_TOKEN, REQUEST_FROM, ROLE_ADMIN, ROLE_DEFAULT, ROLE_HOMEASSISTANT,
|
HEADER_TOKEN,
|
||||||
ROLE_MANAGER, ROLE_BACKUP)
|
REQUEST_FROM,
|
||||||
|
ROLE_ADMIN,
|
||||||
|
ROLE_DEFAULT,
|
||||||
|
ROLE_HOMEASSISTANT,
|
||||||
|
ROLE_MANAGER,
|
||||||
|
ROLE_BACKUP,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
|
||||||
# Block Anytime
|
# Block Anytime
|
||||||
BLACKLIST = re.compile(
|
BLACKLIST = re.compile(
|
||||||
@@ -65,7 +72,7 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
r"|/hardware/.+"
|
r"|/hardware/.+"
|
||||||
r"|/hassos/.+"
|
r"|/hassos/.+"
|
||||||
r"|/supervisor/.+"
|
r"|/supervisor/.+"
|
||||||
r"|/addons(?:/[^/]+/(?!security).+)?"
|
r"|/addons(?:/[^/]+/(?!security).+|/reload)?"
|
||||||
r"|/snapshots.*"
|
r"|/snapshots.*"
|
||||||
r")$"
|
r")$"
|
||||||
),
|
),
|
||||||
@@ -74,6 +81,8 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
|
||||||
|
|
||||||
class SecurityMiddleware(CoreSysAttributes):
|
class SecurityMiddleware(CoreSysAttributes):
|
||||||
"""Security middleware functions."""
|
"""Security middleware functions."""
|
||||||
@@ -104,9 +113,7 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
raise HTTPUnauthorized()
|
raise HTTPUnauthorized()
|
||||||
|
|
||||||
# Home-Assistant
|
# Home-Assistant
|
||||||
# UUID check need removed with 131
|
if hassio_token == self.sys_homeassistant.hassio_token:
|
||||||
if hassio_token in (self.sys_homeassistant.uuid,
|
|
||||||
self.sys_homeassistant.hassio_token):
|
|
||||||
_LOGGER.debug("%s access from Home Assistant", request.path)
|
_LOGGER.debug("%s access from Home Assistant", request.path)
|
||||||
request_from = self.sys_homeassistant
|
request_from = self.sys_homeassistant
|
||||||
|
|
||||||
|
@@ -1,34 +1,57 @@
|
|||||||
"""Init file for Hass.io Supervisor RESTful API."""
|
"""Init file for Hass.io Supervisor RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_ARCH,
|
ATTR_ADDONS,
|
||||||
HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_LOGO, ATTR_REPOSITORY,
|
ATTR_ADDONS_REPOSITORIES,
|
||||||
ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, ATTR_TIMEZONE,
|
ATTR_ARCH,
|
||||||
ATTR_STATE, ATTR_WAIT_BOOT, ATTR_CPU_PERCENT, ATTR_MEMORY_USAGE,
|
ATTR_BLK_READ,
|
||||||
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
ATTR_BLK_WRITE,
|
||||||
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON)
|
ATTR_CHANNEL,
|
||||||
|
ATTR_CPU_PERCENT,
|
||||||
|
ATTR_DESCRIPTON,
|
||||||
|
ATTR_ICON,
|
||||||
|
ATTR_INSTALLED,
|
||||||
|
ATTR_LAST_VERSION,
|
||||||
|
ATTR_LOGO,
|
||||||
|
ATTR_MEMORY_LIMIT,
|
||||||
|
ATTR_MEMORY_USAGE,
|
||||||
|
ATTR_NAME,
|
||||||
|
ATTR_NETWORK_RX,
|
||||||
|
ATTR_NETWORK_TX,
|
||||||
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SLUG,
|
||||||
|
ATTR_STATE,
|
||||||
|
ATTR_TIMEZONE,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WAIT_BOOT,
|
||||||
|
ATTR_IP_ADDRESS,
|
||||||
|
CONTENT_TYPE_BINARY,
|
||||||
|
HASSIO_VERSION,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import WAIT_BOOT, REPOSITORIES, CHANNELS
|
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..utils.validate import validate_timezone
|
from ..utils.validate import validate_timezone
|
||||||
|
from ..validate import CHANNELS, REPOSITORIES, WAIT_BOOT
|
||||||
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_OPTIONS = vol.Schema({
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
vol.Optional(ATTR_CHANNEL): CHANNELS,
|
{
|
||||||
vol.Optional(ATTR_ADDONS_REPOSITORIES): REPOSITORIES,
|
vol.Optional(ATTR_CHANNEL): CHANNELS,
|
||||||
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
vol.Optional(ATTR_ADDONS_REPOSITORIES): REPOSITORIES,
|
||||||
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
|
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
||||||
})
|
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class APISupervisor(CoreSysAttributes):
|
class APISupervisor(CoreSysAttributes):
|
||||||
@@ -40,28 +63,31 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return host information."""
|
"""Return host information."""
|
||||||
list_addons = []
|
list_addons = []
|
||||||
for addon in self.sys_addons.list_addons:
|
for addon in self.sys_addons.list_addons:
|
||||||
if addon.is_installed:
|
if addon.is_installed:
|
||||||
list_addons.append({
|
list_addons.append(
|
||||||
ATTR_NAME: addon.name,
|
{
|
||||||
ATTR_SLUG: addon.slug,
|
ATTR_NAME: addon.name,
|
||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_SLUG: addon.slug,
|
||||||
ATTR_STATE: await addon.state(),
|
ATTR_DESCRIPTON: addon.description,
|
||||||
ATTR_VERSION: addon.last_version,
|
ATTR_STATE: await addon.state(),
|
||||||
ATTR_INSTALLED: addon.version_installed,
|
ATTR_VERSION: addon.latest_version,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_INSTALLED: addon.version_installed,
|
||||||
ATTR_ICON: addon.with_icon,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_LOGO: addon.with_logo,
|
ATTR_ICON: addon.with_icon,
|
||||||
})
|
ATTR_LOGO: addon.with_logo,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: HASSIO_VERSION,
|
ATTR_VERSION: HASSIO_VERSION,
|
||||||
ATTR_LAST_VERSION: self.sys_updater.version_hassio,
|
ATTR_LAST_VERSION: self.sys_updater.version_hassio,
|
||||||
ATTR_CHANNEL: self.sys_updater.channel,
|
ATTR_CHANNEL: self.sys_updater.channel,
|
||||||
ATTR_ARCH: self.sys_arch,
|
ATTR_ARCH: self.sys_supervisor.arch,
|
||||||
|
ATTR_IP_ADDRESS: str(self.sys_supervisor.ip_address),
|
||||||
ATTR_WAIT_BOOT: self.sys_config.wait_boot,
|
ATTR_WAIT_BOOT: self.sys_config.wait_boot,
|
||||||
ATTR_TIMEZONE: self.sys_config.timezone,
|
ATTR_TIMEZONE: self.sys_config.timezone,
|
||||||
ATTR_ADDONS: list_addons,
|
ATTR_ADDONS: list_addons,
|
||||||
@@ -69,7 +95,7 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request: web.Request) -> None:
|
||||||
"""Set Supervisor options."""
|
"""Set Supervisor options."""
|
||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
@@ -88,14 +114,11 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
self.sys_updater.save_data()
|
self.sys_updater.save_data()
|
||||||
self.sys_config.save_data()
|
self.sys_config.save_data()
|
||||||
return True
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request):
|
async def stats(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_supervisor.stats()
|
stats = await self.sys_supervisor.stats()
|
||||||
if not stats:
|
|
||||||
raise APIError("No stats available")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@@ -108,33 +131,21 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update Supervisor OS."""
|
"""Update Supervisor OS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio)
|
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio)
|
||||||
|
|
||||||
if version == self.sys_supervisor.version:
|
if version == self.sys_supervisor.version:
|
||||||
raise APIError("Version {} is already in use".format(version))
|
raise APIError("Version {} is already in use".format(version))
|
||||||
|
await asyncio.shield(self.sys_supervisor.update(version))
|
||||||
return await asyncio.shield(
|
|
||||||
self.sys_supervisor.update(version))
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def reload(self, request):
|
def reload(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Reload add-ons, configuration, etc."""
|
"""Reload add-ons, configuration, etc."""
|
||||||
tasks = [
|
return asyncio.shield(self.sys_updater.reload())
|
||||||
self.sys_updater.reload(),
|
|
||||||
]
|
|
||||||
results, _ = await asyncio.shield(
|
|
||||||
asyncio.wait(tasks))
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
if result.exception() is not None:
|
|
||||||
raise APIError("Some reload task fails!")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return supervisor Docker logs."""
|
"""Return supervisor Docker logs."""
|
||||||
return self.sys_supervisor.logs()
|
return self.sys_supervisor.logs()
|
||||||
|
49
hassio/arch.json
Normal file
49
hassio/arch.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"raspberrypi": [
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"raspberrypi2": [
|
||||||
|
"armv7",
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"raspberrypi3": [
|
||||||
|
"armv7",
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"raspberrypi3-64": [
|
||||||
|
"aarch64",
|
||||||
|
"armv7",
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"tinker": [
|
||||||
|
"armv7",
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"odroid-c2": [
|
||||||
|
"aarch64"
|
||||||
|
],
|
||||||
|
"odroid-xu": [
|
||||||
|
"armv7",
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"orangepi-prime": [
|
||||||
|
"aarch64"
|
||||||
|
],
|
||||||
|
"qemux86": [
|
||||||
|
"i386"
|
||||||
|
],
|
||||||
|
"qemux86-64": [
|
||||||
|
"amd64",
|
||||||
|
"i386"
|
||||||
|
],
|
||||||
|
"qemuarm": [
|
||||||
|
"armhf"
|
||||||
|
],
|
||||||
|
"qemuarm-64": [
|
||||||
|
"aarch64"
|
||||||
|
],
|
||||||
|
"intel-nuc": [
|
||||||
|
"amd64",
|
||||||
|
"i386"
|
||||||
|
]
|
||||||
|
}
|
65
hassio/arch.py
Normal file
65
hassio/arch.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""Handle Arch for underlay maschine/platforms."""
|
||||||
|
import logging
|
||||||
|
from typing import List
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .coresys import CoreSysAttributes, CoreSys
|
||||||
|
from .exceptions import HassioArchNotFound, JsonFileError
|
||||||
|
from .utils.json import read_json_file
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CpuArch(CoreSysAttributes):
|
||||||
|
"""Manage available architectures."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys) -> None:
|
||||||
|
"""Initialize CPU Architecture handler."""
|
||||||
|
self.coresys = coresys
|
||||||
|
self._supported_arch: List[str] = []
|
||||||
|
self._default_arch: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default(self) -> str:
|
||||||
|
"""Return system default arch."""
|
||||||
|
return self._default_arch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supervisor(self) -> str:
|
||||||
|
"""Return supervisor arch."""
|
||||||
|
return self.sys_supervisor.arch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported(self) -> List[str]:
|
||||||
|
"""Return support arch by CPU/Machine."""
|
||||||
|
return self._supported_arch
|
||||||
|
|
||||||
|
async def load(self) -> None:
|
||||||
|
"""Load data and initialize default arch."""
|
||||||
|
try:
|
||||||
|
arch_data = read_json_file(Path(__file__).parent.joinpath("arch.json"))
|
||||||
|
except JsonFileError:
|
||||||
|
_LOGGER.warning("Can't read arch json")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Evaluate current CPU/Platform
|
||||||
|
if not self.sys_machine or self.sys_machine not in arch_data:
|
||||||
|
_LOGGER.warning("Can't detect underlay machine type!")
|
||||||
|
self._default_arch = self.sys_supervisor.arch
|
||||||
|
self._supported_arch.append(self.default)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use configs from arch.json
|
||||||
|
self._supported_arch.extend(arch_data[self.sys_machine])
|
||||||
|
self._default_arch = self.supported[0]
|
||||||
|
|
||||||
|
def is_supported(self, arch_list: List[str]) -> bool:
|
||||||
|
"""Return True if there is a supported arch by this platform."""
|
||||||
|
return not set(self.supported).isdisjoint(set(arch_list))
|
||||||
|
|
||||||
|
def match(self, arch_list: List[str]) -> str:
|
||||||
|
"""Return best match for this CPU/Platform."""
|
||||||
|
for self_arch in self.supported:
|
||||||
|
if self_arch in arch_list:
|
||||||
|
return self_arch
|
||||||
|
raise HassioArchNotFound()
|
@@ -1,44 +1,47 @@
|
|||||||
"""Bootstrap Hass.io."""
|
"""Bootstrap Hass.io."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
|
||||||
from colorlog import ColoredFormatter
|
from colorlog import ColoredFormatter
|
||||||
|
|
||||||
from .core import HassIO
|
|
||||||
from .auth import Auth
|
|
||||||
from .addons import AddonManager
|
from .addons import AddonManager
|
||||||
from .api import RestAPI
|
from .api import RestAPI
|
||||||
|
from .arch import CpuArch
|
||||||
|
from .auth import Auth
|
||||||
from .const import SOCKET_DOCKER
|
from .const import SOCKET_DOCKER
|
||||||
|
from .core import HassIO
|
||||||
from .coresys import CoreSys
|
from .coresys import CoreSys
|
||||||
from .supervisor import Supervisor
|
from .dbus import DBusManager
|
||||||
|
from .discovery import Discovery
|
||||||
|
from .hassos import HassOS
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant import HomeAssistant
|
||||||
|
from .host import HostManager
|
||||||
|
from .ingress import Ingress
|
||||||
|
from .services import ServiceManager
|
||||||
from .snapshots import SnapshotManager
|
from .snapshots import SnapshotManager
|
||||||
|
from .supervisor import Supervisor
|
||||||
from .tasks import Tasks
|
from .tasks import Tasks
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .services import ServiceManager
|
|
||||||
from .discovery import Discovery
|
|
||||||
from .host import HostManager
|
|
||||||
from .dbus import DBusManager
|
|
||||||
from .hassos import HassOS
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ENV_SHARE = 'SUPERVISOR_SHARE'
|
ENV_SHARE = "SUPERVISOR_SHARE"
|
||||||
ENV_NAME = 'SUPERVISOR_NAME'
|
ENV_NAME = "SUPERVISOR_NAME"
|
||||||
ENV_REPO = 'HOMEASSISTANT_REPOSITORY'
|
ENV_REPO = "HOMEASSISTANT_REPOSITORY"
|
||||||
|
|
||||||
MACHINE_ID = Path('/etc/machine-id')
|
MACHINE_ID = Path("/etc/machine-id")
|
||||||
|
|
||||||
|
|
||||||
def initialize_coresys(loop):
|
async def initialize_coresys():
|
||||||
"""Initialize HassIO coresys/objects."""
|
"""Initialize HassIO coresys/objects."""
|
||||||
coresys = CoreSys(loop)
|
coresys = CoreSys()
|
||||||
|
|
||||||
# Initialize core objects
|
# Initialize core objects
|
||||||
coresys.core = HassIO(coresys)
|
coresys.core = HassIO(coresys)
|
||||||
|
coresys.arch = CpuArch(coresys)
|
||||||
coresys.auth = Auth(coresys)
|
coresys.auth = Auth(coresys)
|
||||||
coresys.updater = Updater(coresys)
|
coresys.updater = Updater(coresys)
|
||||||
coresys.api = RestAPI(coresys)
|
coresys.api = RestAPI(coresys)
|
||||||
@@ -47,6 +50,7 @@ def initialize_coresys(loop):
|
|||||||
coresys.addons = AddonManager(coresys)
|
coresys.addons = AddonManager(coresys)
|
||||||
coresys.snapshots = SnapshotManager(coresys)
|
coresys.snapshots = SnapshotManager(coresys)
|
||||||
coresys.host = HostManager(coresys)
|
coresys.host = HostManager(coresys)
|
||||||
|
coresys.ingress = Ingress(coresys)
|
||||||
coresys.tasks = Tasks(coresys)
|
coresys.tasks = Tasks(coresys)
|
||||||
coresys.services = ServiceManager(coresys)
|
coresys.services = ServiceManager(coresys)
|
||||||
coresys.discovery = Discovery(coresys)
|
coresys.discovery = Discovery(coresys)
|
||||||
@@ -70,8 +74,8 @@ def initialize_system_data(coresys):
|
|||||||
# Home Assistant configuration folder
|
# Home Assistant configuration folder
|
||||||
if not config.path_homeassistant.is_dir():
|
if not config.path_homeassistant.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Create Home Assistant configuration folder %s",
|
"Create Home Assistant configuration folder %s", config.path_homeassistant
|
||||||
config.path_homeassistant)
|
)
|
||||||
config.path_homeassistant.mkdir()
|
config.path_homeassistant.mkdir()
|
||||||
|
|
||||||
# hassio ssl folder
|
# hassio ssl folder
|
||||||
@@ -81,18 +85,19 @@ def initialize_system_data(coresys):
|
|||||||
|
|
||||||
# hassio addon data folder
|
# hassio addon data folder
|
||||||
if not config.path_addons_data.is_dir():
|
if not config.path_addons_data.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info("Create Hass.io Add-on data folder %s", config.path_addons_data)
|
||||||
"Create Hass.io Add-on data folder %s", config.path_addons_data)
|
|
||||||
config.path_addons_data.mkdir(parents=True)
|
config.path_addons_data.mkdir(parents=True)
|
||||||
|
|
||||||
if not config.path_addons_local.is_dir():
|
if not config.path_addons_local.is_dir():
|
||||||
_LOGGER.info("Create Hass.io Add-on local repository folder %s",
|
_LOGGER.info(
|
||||||
config.path_addons_local)
|
"Create Hass.io Add-on local repository folder %s", config.path_addons_local
|
||||||
|
)
|
||||||
config.path_addons_local.mkdir(parents=True)
|
config.path_addons_local.mkdir(parents=True)
|
||||||
|
|
||||||
if not config.path_addons_git.is_dir():
|
if not config.path_addons_git.is_dir():
|
||||||
_LOGGER.info("Create Hass.io Add-on git repositories folder %s",
|
_LOGGER.info(
|
||||||
config.path_addons_git)
|
"Create Hass.io Add-on git repositories folder %s", config.path_addons_git
|
||||||
|
)
|
||||||
config.path_addons_git.mkdir(parents=True)
|
config.path_addons_git.mkdir(parents=True)
|
||||||
|
|
||||||
# hassio tmp folder
|
# hassio tmp folder
|
||||||
@@ -134,26 +139,27 @@ def migrate_system_env(coresys):
|
|||||||
def initialize_logging():
|
def initialize_logging():
|
||||||
"""Setup the logging."""
|
"""Setup the logging."""
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
fmt = ("%(asctime)s %(levelname)s (%(threadName)s) "
|
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||||
"[%(name)s] %(message)s")
|
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
||||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
datefmt = "%y-%m-%d %H:%M:%S"
|
||||||
datefmt = '%y-%m-%d %H:%M:%S'
|
|
||||||
|
|
||||||
# suppress overly verbose logs from libraries that aren't helpful
|
# suppress overly verbose logs from libraries that aren't helpful
|
||||||
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
||||||
|
|
||||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
logging.getLogger().handlers[0].setFormatter(
|
||||||
colorfmt,
|
ColoredFormatter(
|
||||||
datefmt=datefmt,
|
colorfmt,
|
||||||
reset=True,
|
datefmt=datefmt,
|
||||||
log_colors={
|
reset=True,
|
||||||
'DEBUG': 'cyan',
|
log_colors={
|
||||||
'INFO': 'green',
|
"DEBUG": "cyan",
|
||||||
'WARNING': 'yellow',
|
"INFO": "green",
|
||||||
'ERROR': 'red',
|
"WARNING": "yellow",
|
||||||
'CRITICAL': 'red',
|
"ERROR": "red",
|
||||||
}
|
"CRITICAL": "red",
|
||||||
))
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_environment():
|
def check_environment():
|
||||||
@@ -172,12 +178,12 @@ def check_environment():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# check socat exec
|
# check socat exec
|
||||||
if not shutil.which('socat'):
|
if not shutil.which("socat"):
|
||||||
_LOGGER.fatal("Can't find socat!")
|
_LOGGER.fatal("Can't find socat!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check socat exec
|
# check socat exec
|
||||||
if not shutil.which('gdbus'):
|
if not shutil.which("gdbus"):
|
||||||
_LOGGER.fatal("Can't find gdbus!")
|
_LOGGER.fatal("Can't find gdbus!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -187,19 +193,16 @@ def check_environment():
|
|||||||
def reg_signal(loop):
|
def reg_signal(loop):
|
||||||
"""Register SIGTERM and SIGKILL to stop system."""
|
"""Register SIGTERM and SIGKILL to stop system."""
|
||||||
try:
|
try:
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(signal.SIGTERM, lambda: loop.call_soon(loop.stop))
|
||||||
signal.SIGTERM, lambda: loop.call_soon(loop.stop))
|
|
||||||
except (ValueError, RuntimeError):
|
except (ValueError, RuntimeError):
|
||||||
_LOGGER.warning("Could not bind to SIGTERM")
|
_LOGGER.warning("Could not bind to SIGTERM")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(signal.SIGHUP, lambda: loop.call_soon(loop.stop))
|
||||||
signal.SIGHUP, lambda: loop.call_soon(loop.stop))
|
|
||||||
except (ValueError, RuntimeError):
|
except (ValueError, RuntimeError):
|
||||||
_LOGGER.warning("Could not bind to SIGHUP")
|
_LOGGER.warning("Could not bind to SIGHUP")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(signal.SIGINT, lambda: loop.call_soon(loop.stop))
|
||||||
signal.SIGINT, lambda: loop.call_soon(loop.stop))
|
|
||||||
except (ValueError, RuntimeError):
|
except (ValueError, RuntimeError):
|
||||||
_LOGGER.warning("Could not bind to SIGINT")
|
_LOGGER.warning("Could not bind to SIGINT")
|
||||||
|
474
hassio/const.py
474
hassio/const.py
@@ -2,17 +2,17 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '141'
|
|
||||||
|
HASSIO_VERSION = "156"
|
||||||
|
|
||||||
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 = "https://s3.amazonaws.com/hassio-version/{channel}.json"
|
||||||
"https://s3.amazonaws.com/hassio-version/{channel}.json"
|
URL_HASSIO_APPARMOR = "https://s3.amazonaws.com/hassio-version/apparmor.txt"
|
||||||
URL_HASSIO_APPARMOR = \
|
|
||||||
"https://s3.amazonaws.com/hassio-version/apparmor.txt"
|
|
||||||
|
|
||||||
URL_HASSOS_OTA = (
|
URL_HASSOS_OTA = (
|
||||||
"https://github.com/home-assistant/hassos/releases/download/"
|
"https://github.com/home-assistant/hassos/releases/download/"
|
||||||
"{version}/hassos_{board}-{version}.raucb")
|
"{version}/hassos_{board}-{version}.raucb"
|
||||||
|
)
|
||||||
|
|
||||||
HASSIO_DATA = Path("/data")
|
HASSIO_DATA = Path("/data")
|
||||||
|
|
||||||
@@ -23,242 +23,280 @@ FILE_HASSIO_HOMEASSISTANT = Path(HASSIO_DATA, "homeassistant.json")
|
|||||||
FILE_HASSIO_UPDATER = Path(HASSIO_DATA, "updater.json")
|
FILE_HASSIO_UPDATER = Path(HASSIO_DATA, "updater.json")
|
||||||
FILE_HASSIO_SERVICES = Path(HASSIO_DATA, "services.json")
|
FILE_HASSIO_SERVICES = Path(HASSIO_DATA, "services.json")
|
||||||
FILE_HASSIO_DISCOVERY = Path(HASSIO_DATA, "discovery.json")
|
FILE_HASSIO_DISCOVERY = Path(HASSIO_DATA, "discovery.json")
|
||||||
|
FILE_HASSIO_INGRESS = Path(HASSIO_DATA, "ingress.json")
|
||||||
|
|
||||||
SOCKET_DOCKER = Path("/var/run/docker.sock")
|
SOCKET_DOCKER = Path("/var/run/docker.sock")
|
||||||
|
|
||||||
DOCKER_NETWORK = 'hassio'
|
DOCKER_NETWORK = "hassio"
|
||||||
DOCKER_NETWORK_MASK = ip_network('172.30.32.0/23')
|
DOCKER_NETWORK_MASK = ip_network("172.30.32.0/23")
|
||||||
DOCKER_NETWORK_RANGE = ip_network('172.30.33.0/24')
|
DOCKER_NETWORK_RANGE = ip_network("172.30.33.0/24")
|
||||||
|
|
||||||
LABEL_VERSION = 'io.hass.version'
|
LABEL_VERSION = "io.hass.version"
|
||||||
LABEL_ARCH = 'io.hass.arch'
|
LABEL_ARCH = "io.hass.arch"
|
||||||
LABEL_TYPE = 'io.hass.type'
|
LABEL_TYPE = "io.hass.type"
|
||||||
LABEL_MACHINE = 'io.hass.machine'
|
LABEL_MACHINE = "io.hass.machine"
|
||||||
|
|
||||||
META_ADDON = 'addon'
|
META_ADDON = "addon"
|
||||||
META_SUPERVISOR = 'supervisor'
|
META_SUPERVISOR = "supervisor"
|
||||||
META_HOMEASSISTANT = 'homeassistant'
|
META_HOMEASSISTANT = "homeassistant"
|
||||||
|
|
||||||
JSON_RESULT = 'result'
|
JSON_RESULT = "result"
|
||||||
JSON_DATA = 'data'
|
JSON_DATA = "data"
|
||||||
JSON_MESSAGE = 'message'
|
JSON_MESSAGE = "message"
|
||||||
|
|
||||||
RESULT_ERROR = 'error'
|
RESULT_ERROR = "error"
|
||||||
RESULT_OK = 'ok'
|
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'
|
CONTENT_TYPE_TEXT = "text/plain"
|
||||||
CONTENT_TYPE_TAR = 'application/tar'
|
CONTENT_TYPE_TAR = "application/tar"
|
||||||
CONTENT_TYPE_URL = 'application/x-www-form-urlencoded'
|
CONTENT_TYPE_URL = "application/x-www-form-urlencoded"
|
||||||
HEADER_HA_ACCESS = 'x-ha-access'
|
HEADER_HA_ACCESS = "X-Ha-Access"
|
||||||
HEADER_TOKEN = 'x-hassio-key'
|
HEADER_TOKEN = "X-Hassio-Key"
|
||||||
|
COOKIE_INGRESS = "ingress_session"
|
||||||
|
|
||||||
ENV_TOKEN = 'HASSIO_TOKEN'
|
ENV_TOKEN = "HASSIO_TOKEN"
|
||||||
ENV_TIME = 'TZ'
|
ENV_TIME = "TZ"
|
||||||
|
|
||||||
REQUEST_FROM = 'HASSIO_FROM'
|
REQUEST_FROM = "HASSIO_FROM"
|
||||||
|
|
||||||
ATTR_MACHINE = 'machine'
|
ATTR_MACHINE = "machine"
|
||||||
ATTR_WAIT_BOOT = 'wait_boot'
|
ATTR_WAIT_BOOT = "wait_boot"
|
||||||
ATTR_DEPLOYMENT = 'deployment'
|
ATTR_DEPLOYMENT = "deployment"
|
||||||
ATTR_WATCHDOG = 'watchdog'
|
ATTR_WATCHDOG = "watchdog"
|
||||||
ATTR_CHANGELOG = 'changelog'
|
ATTR_CHANGELOG = "changelog"
|
||||||
ATTR_DATE = 'date'
|
ATTR_DATE = "date"
|
||||||
ATTR_ARCH = 'arch'
|
ATTR_ARCH = "arch"
|
||||||
ATTR_LONG_DESCRIPTION = 'long_description'
|
ATTR_LONG_DESCRIPTION = "long_description"
|
||||||
ATTR_HOSTNAME = 'hostname'
|
ATTR_HOSTNAME = "hostname"
|
||||||
ATTR_TIMEZONE = 'timezone'
|
ATTR_TIMEZONE = "timezone"
|
||||||
ATTR_ARGS = 'args'
|
ATTR_ARGS = "args"
|
||||||
ATTR_OPERATING_SYSTEM = 'operating_system'
|
ATTR_OPERATING_SYSTEM = "operating_system"
|
||||||
ATTR_CHASSIS = 'chassis'
|
ATTR_CHASSIS = "chassis"
|
||||||
ATTR_TYPE = 'type'
|
ATTR_TYPE = "type"
|
||||||
ATTR_SOURCE = 'source'
|
ATTR_SOURCE = "source"
|
||||||
ATTR_FEATURES = 'features'
|
ATTR_FEATURES = "features"
|
||||||
ATTR_ADDONS = 'addons'
|
ATTR_ADDONS = "addons"
|
||||||
ATTR_PROVIDERS = 'providers'
|
ATTR_PROVIDERS = "providers"
|
||||||
ATTR_VERSION = 'version'
|
ATTR_VERSION = "version"
|
||||||
ATTR_VERSION_LATEST = 'version_latest'
|
ATTR_VERSION_LATEST = "version_latest"
|
||||||
ATTR_AUTO_UART = 'auto_uart'
|
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_CHANNEL = 'channel'
|
ATTR_CHANNEL = "channel"
|
||||||
ATTR_NAME = 'name'
|
ATTR_NAME = "name"
|
||||||
ATTR_SLUG = 'slug'
|
ATTR_SLUG = "slug"
|
||||||
ATTR_DESCRIPTON = 'description'
|
ATTR_DESCRIPTON = "description"
|
||||||
ATTR_STARTUP = 'startup'
|
ATTR_STARTUP = "startup"
|
||||||
ATTR_BOOT = 'boot'
|
ATTR_BOOT = "boot"
|
||||||
ATTR_PORTS = 'ports'
|
ATTR_PORTS = "ports"
|
||||||
ATTR_PORT = 'port'
|
ATTR_PORTS_DESCRIPTION = "ports_description"
|
||||||
ATTR_SSL = 'ssl'
|
ATTR_PORT = "port"
|
||||||
ATTR_MAP = 'map'
|
ATTR_SSL = "ssl"
|
||||||
ATTR_WEBUI = 'webui'
|
ATTR_MAP = "map"
|
||||||
ATTR_OPTIONS = 'options'
|
ATTR_WEBUI = "webui"
|
||||||
ATTR_INSTALLED = 'installed'
|
ATTR_OPTIONS = "options"
|
||||||
ATTR_DETACHED = 'detached'
|
ATTR_INSTALLED = "installed"
|
||||||
ATTR_STATE = 'state'
|
ATTR_DETACHED = "detached"
|
||||||
ATTR_SCHEMA = 'schema'
|
ATTR_STATE = "state"
|
||||||
ATTR_IMAGE = 'image'
|
ATTR_SCHEMA = "schema"
|
||||||
ATTR_ICON = 'icon'
|
ATTR_IMAGE = "image"
|
||||||
ATTR_LOGO = 'logo'
|
ATTR_ICON = "icon"
|
||||||
ATTR_STDIN = 'stdin'
|
ATTR_LOGO = "logo"
|
||||||
ATTR_ADDONS_REPOSITORIES = 'addons_repositories'
|
ATTR_STDIN = "stdin"
|
||||||
ATTR_REPOSITORY = 'repository'
|
ATTR_ADDONS_REPOSITORIES = "addons_repositories"
|
||||||
ATTR_REPOSITORIES = 'repositories'
|
ATTR_REPOSITORY = "repository"
|
||||||
ATTR_URL = 'url'
|
ATTR_REPOSITORIES = "repositories"
|
||||||
ATTR_MAINTAINER = 'maintainer'
|
ATTR_URL = "url"
|
||||||
ATTR_PASSWORD = 'password'
|
ATTR_MAINTAINER = "maintainer"
|
||||||
ATTR_TOTP = 'totp'
|
ATTR_PASSWORD = "password"
|
||||||
ATTR_INITIALIZE = 'initialize'
|
ATTR_TOTP = "totp"
|
||||||
ATTR_LOCATON = 'location'
|
ATTR_INITIALIZE = "initialize"
|
||||||
ATTR_BUILD = 'build'
|
ATTR_LOCATON = "location"
|
||||||
ATTR_DEVICES = 'devices'
|
ATTR_BUILD = "build"
|
||||||
ATTR_ENVIRONMENT = 'environment'
|
ATTR_DEVICES = "devices"
|
||||||
ATTR_HOST_NETWORK = 'host_network'
|
ATTR_ENVIRONMENT = "environment"
|
||||||
ATTR_HOST_PID = 'host_pid'
|
ATTR_HOST_NETWORK = "host_network"
|
||||||
ATTR_HOST_IPC = 'host_ipc'
|
ATTR_HOST_PID = "host_pid"
|
||||||
ATTR_HOST_DBUS = 'host_dbus'
|
ATTR_HOST_IPC = "host_ipc"
|
||||||
ATTR_NETWORK = 'network'
|
ATTR_HOST_DBUS = "host_dbus"
|
||||||
ATTR_TMPFS = 'tmpfs'
|
ATTR_NETWORK = "network"
|
||||||
ATTR_PRIVILEGED = 'privileged'
|
ATTR_NETWORK_DESCRIPTION = "network_description"
|
||||||
ATTR_USER = 'user'
|
ATTR_TMPFS = "tmpfs"
|
||||||
ATTR_SYSTEM = 'system'
|
ATTR_PRIVILEGED = "privileged"
|
||||||
ATTR_SNAPSHOTS = 'snapshots'
|
ATTR_USER = "user"
|
||||||
ATTR_HOMEASSISTANT = 'homeassistant'
|
ATTR_SYSTEM = "system"
|
||||||
ATTR_HASSIO = 'hassio'
|
ATTR_SNAPSHOTS = "snapshots"
|
||||||
ATTR_HASSIO_API = 'hassio_api'
|
ATTR_HOMEASSISTANT = "homeassistant"
|
||||||
ATTR_HOMEASSISTANT_API = 'homeassistant_api'
|
ATTR_HASSIO = "hassio"
|
||||||
ATTR_UUID = 'uuid'
|
ATTR_HASSIO_API = "hassio_api"
|
||||||
ATTR_FOLDERS = 'folders'
|
ATTR_HOMEASSISTANT_API = "homeassistant_api"
|
||||||
ATTR_SIZE = 'size'
|
ATTR_UUID = "uuid"
|
||||||
ATTR_TYPE = 'type'
|
ATTR_FOLDERS = "folders"
|
||||||
ATTR_TIMEOUT = 'timeout'
|
ATTR_SIZE = "size"
|
||||||
ATTR_AUTO_UPDATE = 'auto_update'
|
ATTR_TYPE = "type"
|
||||||
ATTR_CUSTOM = 'custom'
|
ATTR_TIMEOUT = "timeout"
|
||||||
ATTR_AUDIO = 'audio'
|
ATTR_AUTO_UPDATE = "auto_update"
|
||||||
ATTR_AUDIO_INPUT = 'audio_input'
|
ATTR_CUSTOM = "custom"
|
||||||
ATTR_AUDIO_OUTPUT = 'audio_output'
|
ATTR_AUDIO = "audio"
|
||||||
ATTR_INPUT = 'input'
|
ATTR_AUDIO_INPUT = "audio_input"
|
||||||
ATTR_OUTPUT = 'output'
|
ATTR_AUDIO_OUTPUT = "audio_output"
|
||||||
ATTR_DISK = 'disk'
|
ATTR_INPUT = "input"
|
||||||
ATTR_SERIAL = 'serial'
|
ATTR_OUTPUT = "output"
|
||||||
ATTR_SECURITY = 'security'
|
ATTR_DISK = "disk"
|
||||||
ATTR_BUILD_FROM = 'build_from'
|
ATTR_SERIAL = "serial"
|
||||||
ATTR_SQUASH = 'squash'
|
ATTR_SECURITY = "security"
|
||||||
ATTR_GPIO = 'gpio'
|
ATTR_BUILD_FROM = "build_from"
|
||||||
ATTR_LEGACY = 'legacy'
|
ATTR_SQUASH = "squash"
|
||||||
ATTR_ADDONS_CUSTOM_LIST = 'addons_custom_list'
|
ATTR_GPIO = "gpio"
|
||||||
ATTR_CPU_PERCENT = 'cpu_percent'
|
ATTR_LEGACY = "legacy"
|
||||||
ATTR_NETWORK_RX = 'network_rx'
|
ATTR_ADDONS_CUSTOM_LIST = "addons_custom_list"
|
||||||
ATTR_NETWORK_TX = 'network_tx'
|
ATTR_CPU_PERCENT = "cpu_percent"
|
||||||
ATTR_MEMORY_LIMIT = 'memory_limit'
|
ATTR_NETWORK_RX = "network_rx"
|
||||||
ATTR_MEMORY_USAGE = 'memory_usage'
|
ATTR_NETWORK_TX = "network_tx"
|
||||||
ATTR_BLK_READ = 'blk_read'
|
ATTR_MEMORY_LIMIT = "memory_limit"
|
||||||
ATTR_BLK_WRITE = 'blk_write'
|
ATTR_MEMORY_USAGE = "memory_usage"
|
||||||
ATTR_ADDON = 'addon'
|
ATTR_BLK_READ = "blk_read"
|
||||||
ATTR_AVAILABLE = 'available'
|
ATTR_BLK_WRITE = "blk_write"
|
||||||
ATTR_HOST = 'host'
|
ATTR_ADDON = "addon"
|
||||||
ATTR_USERNAME = 'username'
|
ATTR_AVAILABLE = "available"
|
||||||
ATTR_PROTOCOL = 'protocol'
|
ATTR_HOST = "host"
|
||||||
ATTR_DISCOVERY = 'discovery'
|
ATTR_USERNAME = "username"
|
||||||
ATTR_CONFIG = 'config'
|
ATTR_DISCOVERY = "discovery"
|
||||||
ATTR_SERVICES = 'services'
|
ATTR_CONFIG = "config"
|
||||||
ATTR_SERVICE = 'service'
|
ATTR_SERVICES = "services"
|
||||||
ATTR_DISCOVERY = 'discovery'
|
ATTR_SERVICE = "service"
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_DISCOVERY = "discovery"
|
||||||
ATTR_CRYPTO = 'crypto'
|
ATTR_PROTECTED = "protected"
|
||||||
ATTR_BRANCH = 'branch'
|
ATTR_CRYPTO = "crypto"
|
||||||
ATTR_KERNEL = 'kernel'
|
ATTR_BRANCH = "branch"
|
||||||
ATTR_APPARMOR = 'apparmor'
|
ATTR_KERNEL = "kernel"
|
||||||
ATTR_DEVICETREE = 'devicetree'
|
ATTR_APPARMOR = "apparmor"
|
||||||
ATTR_CPE = 'cpe'
|
ATTR_DEVICETREE = "devicetree"
|
||||||
ATTR_BOARD = 'board'
|
ATTR_CPE = "cpe"
|
||||||
ATTR_HASSOS = 'hassos'
|
ATTR_BOARD = "board"
|
||||||
ATTR_HASSOS_CLI = 'hassos_cli'
|
ATTR_HASSOS = "hassos"
|
||||||
ATTR_VERSION_CLI = 'version_cli'
|
ATTR_HASSOS_CLI = "hassos_cli"
|
||||||
ATTR_VERSION_CLI_LATEST = 'version_cli_latest'
|
ATTR_VERSION_CLI = "version_cli"
|
||||||
ATTR_REFRESH_TOKEN = 'refresh_token'
|
ATTR_VERSION_CLI_LATEST = "version_cli_latest"
|
||||||
ATTR_ACCESS_TOKEN = 'access_token'
|
ATTR_REFRESH_TOKEN = "refresh_token"
|
||||||
ATTR_DOCKER_API = 'docker_api'
|
ATTR_ACCESS_TOKEN = "access_token"
|
||||||
ATTR_FULL_ACCESS = 'full_access'
|
ATTR_DOCKER_API = "docker_api"
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_FULL_ACCESS = "full_access"
|
||||||
ATTR_RATING = 'rating'
|
ATTR_PROTECTED = "protected"
|
||||||
ATTR_HASSIO_ROLE = 'hassio_role'
|
ATTR_RATING = "rating"
|
||||||
ATTR_SUPERVISOR = 'supervisor'
|
ATTR_HASSIO_ROLE = "hassio_role"
|
||||||
ATTR_AUTH_API = 'auth_api'
|
ATTR_SUPERVISOR = "supervisor"
|
||||||
|
ATTR_AUTH_API = "auth_api"
|
||||||
|
ATTR_KERNEL_MODULES = "kernel_modules"
|
||||||
|
ATTR_SUPPORTED_ARCH = "supported_arch"
|
||||||
|
ATTR_INGRESS = "ingress"
|
||||||
|
ATTR_INGRESS_PORT = "ingress_port"
|
||||||
|
ATTR_INGRESS_ENTRY = "ingress_entry"
|
||||||
|
ATTR_INGRESS_TOKEN = "ingress_token"
|
||||||
|
ATTR_INGRESS_URL = "ingress_url"
|
||||||
|
ATTR_IP_ADDRESS = "ip_address"
|
||||||
|
ATTR_SESSION = "session"
|
||||||
|
|
||||||
SERVICE_MQTT = 'mqtt'
|
PROVIDE_SERVICE = "provide"
|
||||||
PROVIDE_SERVICE = 'provide'
|
NEED_SERVICE = "need"
|
||||||
NEED_SERVICE = 'need'
|
WANT_SERVICE = "want"
|
||||||
WANT_SERVICE = 'want'
|
|
||||||
|
|
||||||
STARTUP_INITIALIZE = 'initialize'
|
STARTUP_INITIALIZE = "initialize"
|
||||||
STARTUP_SYSTEM = 'system'
|
STARTUP_SYSTEM = "system"
|
||||||
STARTUP_SERVICES = 'services'
|
STARTUP_SERVICES = "services"
|
||||||
STARTUP_APPLICATION = 'application'
|
STARTUP_APPLICATION = "application"
|
||||||
STARTUP_ONCE = 'once'
|
STARTUP_ONCE = "once"
|
||||||
|
|
||||||
BOOT_AUTO = 'auto'
|
STARTUP_ALL = [
|
||||||
BOOT_MANUAL = 'manual'
|
STARTUP_ONCE,
|
||||||
|
STARTUP_INITIALIZE,
|
||||||
|
STARTUP_SYSTEM,
|
||||||
|
STARTUP_SERVICES,
|
||||||
|
STARTUP_APPLICATION,
|
||||||
|
]
|
||||||
|
|
||||||
STATE_STARTED = 'started'
|
BOOT_AUTO = "auto"
|
||||||
STATE_STOPPED = 'stopped'
|
BOOT_MANUAL = "manual"
|
||||||
STATE_NONE = 'none'
|
|
||||||
|
|
||||||
MAP_CONFIG = 'config'
|
STATE_STARTED = "started"
|
||||||
MAP_SSL = 'ssl'
|
STATE_STOPPED = "stopped"
|
||||||
MAP_ADDONS = 'addons'
|
STATE_NONE = "none"
|
||||||
MAP_BACKUP = 'backup'
|
|
||||||
MAP_SHARE = 'share'
|
|
||||||
|
|
||||||
ARCH_ARMHF = 'armhf'
|
MAP_CONFIG = "config"
|
||||||
ARCH_AARCH64 = 'aarch64'
|
MAP_SSL = "ssl"
|
||||||
ARCH_AMD64 = 'amd64'
|
MAP_ADDONS = "addons"
|
||||||
ARCH_I386 = 'i386'
|
MAP_BACKUP = "backup"
|
||||||
|
MAP_SHARE = "share"
|
||||||
|
|
||||||
CHANNEL_STABLE = 'stable'
|
ARCH_ARMHF = "armhf"
|
||||||
CHANNEL_BETA = 'beta'
|
ARCH_ARMV7 = "armv7"
|
||||||
CHANNEL_DEV = 'dev'
|
ARCH_AARCH64 = "aarch64"
|
||||||
|
ARCH_AMD64 = "amd64"
|
||||||
|
ARCH_I386 = "i386"
|
||||||
|
|
||||||
REPOSITORY_CORE = 'core'
|
ARCH_ALL = [ARCH_ARMHF, ARCH_ARMV7, ARCH_AARCH64, ARCH_AMD64, ARCH_I386]
|
||||||
REPOSITORY_LOCAL = 'local'
|
|
||||||
|
|
||||||
FOLDER_HOMEASSISTANT = 'homeassistant'
|
CHANNEL_STABLE = "stable"
|
||||||
FOLDER_SHARE = 'share'
|
CHANNEL_BETA = "beta"
|
||||||
FOLDER_ADDONS = 'addons/local'
|
CHANNEL_DEV = "dev"
|
||||||
FOLDER_SSL = 'ssl'
|
|
||||||
|
|
||||||
SNAPSHOT_FULL = 'full'
|
REPOSITORY_CORE = "core"
|
||||||
SNAPSHOT_PARTIAL = 'partial'
|
REPOSITORY_LOCAL = "local"
|
||||||
|
|
||||||
CRYPTO_AES128 = 'aes128'
|
FOLDER_HOMEASSISTANT = "homeassistant"
|
||||||
|
FOLDER_SHARE = "share"
|
||||||
|
FOLDER_ADDONS = "addons/local"
|
||||||
|
FOLDER_SSL = "ssl"
|
||||||
|
|
||||||
SECURITY_PROFILE = 'profile'
|
SNAPSHOT_FULL = "full"
|
||||||
SECURITY_DEFAULT = 'default'
|
SNAPSHOT_PARTIAL = "partial"
|
||||||
SECURITY_DISABLE = 'disable'
|
|
||||||
|
|
||||||
PRIVILEGED_NET_ADMIN = 'NET_ADMIN'
|
CRYPTO_AES128 = "aes128"
|
||||||
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'
|
|
||||||
PRIVILEGED_SYS_PTRACE = 'SYS_PTRACE'
|
|
||||||
PRIVILEGED_DAC_READ_SEARCH = 'DAC_READ_SEARCH'
|
|
||||||
|
|
||||||
FEATURES_SHUTDOWN = 'shutdown'
|
SECURITY_PROFILE = "profile"
|
||||||
FEATURES_REBOOT = 'reboot'
|
SECURITY_DEFAULT = "default"
|
||||||
FEATURES_HASSOS = 'hassos'
|
SECURITY_DISABLE = "disable"
|
||||||
FEATURES_HOSTNAME = 'hostname'
|
|
||||||
FEATURES_SERVICES = 'services'
|
|
||||||
|
|
||||||
ROLE_DEFAULT = 'default'
|
PRIVILEGED_NET_ADMIN = "NET_ADMIN"
|
||||||
ROLE_HOMEASSISTANT = 'homeassistant'
|
PRIVILEGED_SYS_ADMIN = "SYS_ADMIN"
|
||||||
ROLE_BACKUP = 'backup'
|
PRIVILEGED_SYS_RAWIO = "SYS_RAWIO"
|
||||||
ROLE_MANAGER = 'manager'
|
PRIVILEGED_IPC_LOCK = "IPC_LOCK"
|
||||||
ROLE_ADMIN = 'admin'
|
PRIVILEGED_SYS_TIME = "SYS_TIME"
|
||||||
|
PRIVILEGED_SYS_NICE = "SYS_NICE"
|
||||||
|
PRIVILEGED_SYS_MODULE = "SYS_MODULE"
|
||||||
|
PRIVILEGED_SYS_RESOURCE = "SYS_RESOURCE"
|
||||||
|
PRIVILEGED_SYS_PTRACE = "SYS_PTRACE"
|
||||||
|
PRIVILEGED_DAC_READ_SEARCH = "DAC_READ_SEARCH"
|
||||||
|
|
||||||
CHAN_ID = 'chan_id'
|
PRIVILEGED_ALL = [
|
||||||
CHAN_TYPE = 'chan_type'
|
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_MODULE,
|
||||||
|
PRIVILEGED_DAC_READ_SEARCH,
|
||||||
|
]
|
||||||
|
|
||||||
|
FEATURES_SHUTDOWN = "shutdown"
|
||||||
|
FEATURES_REBOOT = "reboot"
|
||||||
|
FEATURES_HASSOS = "hassos"
|
||||||
|
FEATURES_HOSTNAME = "hostname"
|
||||||
|
FEATURES_SERVICES = "services"
|
||||||
|
|
||||||
|
ROLE_DEFAULT = "default"
|
||||||
|
ROLE_HOMEASSISTANT = "homeassistant"
|
||||||
|
ROLE_BACKUP = "backup"
|
||||||
|
ROLE_MANAGER = "manager"
|
||||||
|
ROLE_ADMIN = "admin"
|
||||||
|
|
||||||
|
ROLE_ALL = [ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_BACKUP, ROLE_MANAGER, ROLE_ADMIN]
|
||||||
|
|
||||||
|
CHAN_ID = "chan_id"
|
||||||
|
CHAN_TYPE = "chan_type"
|
||||||
|
@@ -7,7 +7,11 @@ import async_timeout
|
|||||||
|
|
||||||
from .coresys import CoreSysAttributes
|
from .coresys import CoreSysAttributes
|
||||||
from .const import (
|
from .const import (
|
||||||
STARTUP_SYSTEM, STARTUP_SERVICES, STARTUP_APPLICATION, STARTUP_INITIALIZE)
|
STARTUP_SYSTEM,
|
||||||
|
STARTUP_SERVICES,
|
||||||
|
STARTUP_APPLICATION,
|
||||||
|
STARTUP_INITIALIZE,
|
||||||
|
)
|
||||||
from .exceptions import HassioError, HomeAssistantError
|
from .exceptions import HassioError, HomeAssistantError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -31,12 +35,15 @@ class HassIO(CoreSysAttributes):
|
|||||||
# Load Host
|
# Load Host
|
||||||
await self.sys_host.load()
|
await self.sys_host.load()
|
||||||
|
|
||||||
# Load HassOS
|
|
||||||
await self.sys_hassos.load()
|
|
||||||
|
|
||||||
# Load Home Assistant
|
# Load Home Assistant
|
||||||
await self.sys_homeassistant.load()
|
await self.sys_homeassistant.load()
|
||||||
|
|
||||||
|
# Load CPU/Arch
|
||||||
|
await self.sys_arch.load()
|
||||||
|
|
||||||
|
# Load HassOS
|
||||||
|
await self.sys_hassos.load()
|
||||||
|
|
||||||
# Load Add-ons
|
# Load Add-ons
|
||||||
await self.sys_addons.load()
|
await self.sys_addons.load()
|
||||||
|
|
||||||
@@ -55,18 +62,20 @@ class HassIO(CoreSysAttributes):
|
|||||||
# Load discovery
|
# Load discovery
|
||||||
await self.sys_discovery.load()
|
await self.sys_discovery.load()
|
||||||
|
|
||||||
|
# Load ingress
|
||||||
|
await self.sys_ingress.load()
|
||||||
|
|
||||||
# start dns forwarding
|
# start dns forwarding
|
||||||
self.sys_create_task(self.sys_dns.start())
|
self.sys_create_task(self.sys_dns.start())
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Start Hass.io orchestration."""
|
"""Start Hass.io orchestration."""
|
||||||
# on release channel, try update itself
|
# on release channel, try update itself
|
||||||
# on dev mode, only read new versions
|
if self.sys_supervisor.need_update:
|
||||||
if not self.sys_dev and self.sys_supervisor.need_update:
|
if self.sys_dev:
|
||||||
if await self.sys_supervisor.update():
|
_LOGGER.warning("Ignore Hass.io updates on dev!")
|
||||||
|
elif await self.sys_supervisor.update():
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
_LOGGER.info("Ignore Hass.io auto updates on dev channel")
|
|
||||||
|
|
||||||
# start api
|
# start api
|
||||||
await self.sys_api.start()
|
await self.sys_api.start()
|
||||||
@@ -106,7 +115,7 @@ class HassIO(CoreSysAttributes):
|
|||||||
await self.sys_tasks.load()
|
await self.sys_tasks.load()
|
||||||
|
|
||||||
# If landingpage / run upgrade in background
|
# If landingpage / run upgrade in background
|
||||||
if self.sys_homeassistant.version == 'landingpage':
|
if self.sys_homeassistant.version == "landingpage":
|
||||||
self.sys_create_task(self.sys_homeassistant.install())
|
self.sys_create_task(self.sys_homeassistant.install())
|
||||||
|
|
||||||
_LOGGER.info("Hass.io is up and running")
|
_LOGGER.info("Hass.io is up and running")
|
||||||
@@ -119,12 +128,15 @@ class HassIO(CoreSysAttributes):
|
|||||||
# process async stop tasks
|
# process async stop tasks
|
||||||
try:
|
try:
|
||||||
with async_timeout.timeout(10):
|
with async_timeout.timeout(10):
|
||||||
await asyncio.wait([
|
await asyncio.wait(
|
||||||
self.sys_api.stop(),
|
[
|
||||||
self.sys_dns.stop(),
|
self.sys_api.stop(),
|
||||||
self.sys_websession.close(),
|
self.sys_dns.stop(),
|
||||||
self.sys_websession_ssl.close()
|
self.sys_websession.close(),
|
||||||
])
|
self.sys_websession_ssl.close(),
|
||||||
|
self.sys_ingress.unload(),
|
||||||
|
]
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
_LOGGER.warning("Force Shutdown!")
|
_LOGGER.warning("Force Shutdown!")
|
||||||
|
|
||||||
|
@@ -1,300 +1,474 @@
|
|||||||
"""Handle core shared data."""
|
"""Handle core shared data."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .const import CHANNEL_DEV
|
|
||||||
from .config import CoreConfig
|
from .config import CoreConfig
|
||||||
|
from .const import CHANNEL_DEV
|
||||||
from .docker import DockerAPI
|
from .docker import DockerAPI
|
||||||
from .misc.dns import DNSForward
|
from .misc.dns import DNSForward
|
||||||
from .misc.hardware import Hardware
|
from .misc.hardware import Hardware
|
||||||
from .misc.scheduler import Scheduler
|
from .misc.scheduler import Scheduler
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .addons import AddonManager
|
||||||
|
from .api import RestAPI
|
||||||
|
from .arch import CpuArch
|
||||||
|
from .auth import Auth
|
||||||
|
from .core import HassIO
|
||||||
|
from .dbus import DBusManager
|
||||||
|
from .discovery import Discovery
|
||||||
|
from .hassos import HassOS
|
||||||
|
from .homeassistant import HomeAssistant
|
||||||
|
from .host import HostManager
|
||||||
|
from .ingress import Ingress
|
||||||
|
from .services import ServiceManager
|
||||||
|
from .snapshots import SnapshotManager
|
||||||
|
from .supervisor import Supervisor
|
||||||
|
from .tasks import Tasks
|
||||||
|
from .updater import Updater
|
||||||
|
|
||||||
|
|
||||||
class CoreSys:
|
class CoreSys:
|
||||||
"""Class that handle all shared data."""
|
"""Class that handle all shared data."""
|
||||||
|
|
||||||
def __init__(self, loop):
|
def __init__(self):
|
||||||
"""Initialize coresys."""
|
"""Initialize coresys."""
|
||||||
# Static attributes
|
# Static attributes
|
||||||
self.exit_code = 0
|
self.machine_id: str = None
|
||||||
self.machine_id = None
|
|
||||||
|
|
||||||
# External objects
|
# External objects
|
||||||
self._loop = loop
|
self._loop: asyncio.BaseEventLoop = asyncio.get_running_loop()
|
||||||
self._websession = aiohttp.ClientSession(loop=loop)
|
self._websession: aiohttp.ClientSession = aiohttp.ClientSession()
|
||||||
self._websession_ssl = aiohttp.ClientSession(
|
self._websession_ssl: aiohttp.ClientSession = aiohttp.ClientSession(
|
||||||
connector=aiohttp.TCPConnector(verify_ssl=False), loop=loop)
|
connector=aiohttp.TCPConnector(ssl=False))
|
||||||
|
|
||||||
# Global objects
|
# Global objects
|
||||||
self._config = CoreConfig()
|
self._config: CoreConfig = CoreConfig()
|
||||||
self._hardware = Hardware()
|
self._hardware: Hardware = Hardware()
|
||||||
self._docker = DockerAPI()
|
self._docker: DockerAPI = DockerAPI()
|
||||||
self._scheduler = Scheduler(loop=loop)
|
self._scheduler: Scheduler = Scheduler()
|
||||||
self._dns = DNSForward(loop=loop)
|
self._dns: DNSForward = DNSForward()
|
||||||
|
|
||||||
# Internal objects pointers
|
# Internal objects pointers
|
||||||
self._core = None
|
self._core: HassIO = None
|
||||||
self._auth = None
|
self._arch: CpuArch = None
|
||||||
self._homeassistant = None
|
self._auth: Auth = None
|
||||||
self._supervisor = None
|
self._homeassistant: HomeAssistant = None
|
||||||
self._addons = None
|
self._supervisor: Supervisor = None
|
||||||
self._api = None
|
self._addons: AddonManager = None
|
||||||
self._updater = None
|
self._api: RestAPI = None
|
||||||
self._snapshots = None
|
self._updater: Updater = None
|
||||||
self._tasks = None
|
self._snapshots: SnapshotManager = None
|
||||||
self._host = None
|
self._tasks: Tasks = None
|
||||||
self._dbus = None
|
self._host: HostManager = None
|
||||||
self._hassos = None
|
self._ingress: Ingress = None
|
||||||
self._services = None
|
self._dbus: DBusManager = None
|
||||||
self._discovery = None
|
self._hassos: HassOS = None
|
||||||
|
self._services: ServiceManager = None
|
||||||
|
self._discovery: Discovery = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self):
|
def machine(self) -> str:
|
||||||
"""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 the Hass.io system."""
|
"""Return running machine type of the Hass.io system."""
|
||||||
if self._homeassistant:
|
if self._homeassistant:
|
||||||
return self._homeassistant.machine
|
return self._homeassistant.machine
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dev(self):
|
def dev(self) -> str:
|
||||||
"""Return True if we run dev mode."""
|
"""Return True if we run dev mode."""
|
||||||
return self._updater.channel == CHANNEL_DEV
|
return self._updater.channel == CHANNEL_DEV
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timezone(self):
|
def timezone(self) -> str:
|
||||||
"""Return timezone."""
|
"""Return timezone."""
|
||||||
return self._config.timezone
|
return self._config.timezone
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop(self):
|
def loop(self) -> asyncio.BaseEventLoop:
|
||||||
"""Return loop object."""
|
"""Return loop object."""
|
||||||
return self._loop
|
return self._loop
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def websession(self):
|
def websession(self) -> aiohttp.ClientSession:
|
||||||
"""Return websession object."""
|
"""Return websession object."""
|
||||||
return self._websession
|
return self._websession
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def websession_ssl(self):
|
def websession_ssl(self) -> aiohttp.ClientSession:
|
||||||
"""Return websession object with disabled SSL."""
|
"""Return websession object with disabled SSL."""
|
||||||
return self._websession_ssl
|
return self._websession_ssl
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config(self):
|
def config(self) -> CoreConfig:
|
||||||
"""Return CoreConfig object."""
|
"""Return CoreConfig object."""
|
||||||
return self._config
|
return self._config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hardware(self):
|
def hardware(self) -> Hardware:
|
||||||
"""Return Hardware object."""
|
"""Return Hardware object."""
|
||||||
return self._hardware
|
return self._hardware
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker(self):
|
def docker(self) -> DockerAPI:
|
||||||
"""Return DockerAPI object."""
|
"""Return DockerAPI object."""
|
||||||
return self._docker
|
return self._docker
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scheduler(self):
|
def scheduler(self) -> Scheduler:
|
||||||
"""Return Scheduler object."""
|
"""Return Scheduler object."""
|
||||||
return self._scheduler
|
return self._scheduler
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dns(self):
|
def dns(self) -> DNSForward:
|
||||||
"""Return DNSForward object."""
|
"""Return DNSForward object."""
|
||||||
return self._dns
|
return self._dns
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def core(self):
|
def core(self) -> HassIO:
|
||||||
"""Return HassIO object."""
|
"""Return HassIO object."""
|
||||||
return self._core
|
return self._core
|
||||||
|
|
||||||
@core.setter
|
@core.setter
|
||||||
def core(self, value):
|
def core(self, value: HassIO):
|
||||||
"""Set a Hass.io object."""
|
"""Set a Hass.io object."""
|
||||||
if self._core:
|
if self._core:
|
||||||
raise RuntimeError("Hass.io already set!")
|
raise RuntimeError("Hass.io already set!")
|
||||||
self._core = value
|
self._core = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth(self):
|
def arch(self) -> CpuArch:
|
||||||
|
"""Return CpuArch object."""
|
||||||
|
return self._arch
|
||||||
|
|
||||||
|
@arch.setter
|
||||||
|
def arch(self, value: CpuArch):
|
||||||
|
"""Set a CpuArch object."""
|
||||||
|
if self._arch:
|
||||||
|
raise RuntimeError("CpuArch already set!")
|
||||||
|
self._arch = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auth(self) -> Auth:
|
||||||
"""Return Auth object."""
|
"""Return Auth object."""
|
||||||
return self._auth
|
return self._auth
|
||||||
|
|
||||||
@auth.setter
|
@auth.setter
|
||||||
def auth(self, value):
|
def auth(self, value: Auth):
|
||||||
"""Set a Auth object."""
|
"""Set a Auth object."""
|
||||||
if self._auth:
|
if self._auth:
|
||||||
raise RuntimeError("Auth already set!")
|
raise RuntimeError("Auth already set!")
|
||||||
self._auth = value
|
self._auth = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant(self):
|
def homeassistant(self) -> HomeAssistant:
|
||||||
"""Return Home Assistant object."""
|
"""Return Home Assistant object."""
|
||||||
return self._homeassistant
|
return self._homeassistant
|
||||||
|
|
||||||
@homeassistant.setter
|
@homeassistant.setter
|
||||||
def homeassistant(self, value):
|
def homeassistant(self, value: HomeAssistant):
|
||||||
"""Set a HomeAssistant object."""
|
"""Set a HomeAssistant object."""
|
||||||
if self._homeassistant:
|
if self._homeassistant:
|
||||||
raise RuntimeError("Home Assistant already set!")
|
raise RuntimeError("Home Assistant already set!")
|
||||||
self._homeassistant = value
|
self._homeassistant = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supervisor(self):
|
def supervisor(self) -> Supervisor:
|
||||||
"""Return Supervisor object."""
|
"""Return Supervisor object."""
|
||||||
return self._supervisor
|
return self._supervisor
|
||||||
|
|
||||||
@supervisor.setter
|
@supervisor.setter
|
||||||
def supervisor(self, value):
|
def supervisor(self, value: Supervisor):
|
||||||
"""Set a Supervisor object."""
|
"""Set a Supervisor object."""
|
||||||
if self._supervisor:
|
if self._supervisor:
|
||||||
raise RuntimeError("Supervisor already set!")
|
raise RuntimeError("Supervisor already set!")
|
||||||
self._supervisor = value
|
self._supervisor = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self) -> RestAPI:
|
||||||
"""Return API object."""
|
"""Return API object."""
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
@api.setter
|
@api.setter
|
||||||
def api(self, value):
|
def api(self, value: RestAPI):
|
||||||
"""Set an API object."""
|
"""Set an API object."""
|
||||||
if self._api:
|
if self._api:
|
||||||
raise RuntimeError("API already set!")
|
raise RuntimeError("API already set!")
|
||||||
self._api = value
|
self._api = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def updater(self):
|
def updater(self) -> Updater:
|
||||||
"""Return Updater object."""
|
"""Return Updater object."""
|
||||||
return self._updater
|
return self._updater
|
||||||
|
|
||||||
@updater.setter
|
@updater.setter
|
||||||
def updater(self, value):
|
def updater(self, value: Updater):
|
||||||
"""Set a Updater object."""
|
"""Set a Updater object."""
|
||||||
if self._updater:
|
if self._updater:
|
||||||
raise RuntimeError("Updater already set!")
|
raise RuntimeError("Updater already set!")
|
||||||
self._updater = value
|
self._updater = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addons(self):
|
def addons(self) -> AddonManager:
|
||||||
"""Return AddonManager object."""
|
"""Return AddonManager object."""
|
||||||
return self._addons
|
return self._addons
|
||||||
|
|
||||||
@addons.setter
|
@addons.setter
|
||||||
def addons(self, value):
|
def addons(self, value: AddonManager):
|
||||||
"""Set a AddonManager object."""
|
"""Set a AddonManager object."""
|
||||||
if self._addons:
|
if self._addons:
|
||||||
raise RuntimeError("AddonManager already set!")
|
raise RuntimeError("AddonManager already set!")
|
||||||
self._addons = value
|
self._addons = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def snapshots(self):
|
def snapshots(self) -> SnapshotManager:
|
||||||
"""Return SnapshotManager object."""
|
"""Return SnapshotManager object."""
|
||||||
return self._snapshots
|
return self._snapshots
|
||||||
|
|
||||||
@snapshots.setter
|
@snapshots.setter
|
||||||
def snapshots(self, value):
|
def snapshots(self, value: SnapshotManager):
|
||||||
"""Set a SnapshotManager object."""
|
"""Set a SnapshotManager object."""
|
||||||
if self._snapshots:
|
if self._snapshots:
|
||||||
raise RuntimeError("SnapshotsManager already set!")
|
raise RuntimeError("SnapshotsManager already set!")
|
||||||
self._snapshots = value
|
self._snapshots = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tasks(self):
|
def tasks(self) -> Tasks:
|
||||||
"""Return Tasks object."""
|
"""Return Tasks object."""
|
||||||
return self._tasks
|
return self._tasks
|
||||||
|
|
||||||
@tasks.setter
|
@tasks.setter
|
||||||
def tasks(self, value):
|
def tasks(self, value: Tasks):
|
||||||
"""Set a Tasks object."""
|
"""Set a Tasks object."""
|
||||||
if self._tasks:
|
if self._tasks:
|
||||||
raise RuntimeError("Tasks already set!")
|
raise RuntimeError("Tasks already set!")
|
||||||
self._tasks = value
|
self._tasks = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self):
|
def services(self) -> ServiceManager:
|
||||||
"""Return ServiceManager object."""
|
"""Return ServiceManager object."""
|
||||||
return self._services
|
return self._services
|
||||||
|
|
||||||
@services.setter
|
@services.setter
|
||||||
def services(self, value):
|
def services(self, value: ServiceManager):
|
||||||
"""Set a ServiceManager object."""
|
"""Set a ServiceManager object."""
|
||||||
if self._services:
|
if self._services:
|
||||||
raise RuntimeError("Services already set!")
|
raise RuntimeError("Services already set!")
|
||||||
self._services = value
|
self._services = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discovery(self):
|
def discovery(self) -> Discovery:
|
||||||
"""Return ServiceManager object."""
|
"""Return ServiceManager object."""
|
||||||
return self._discovery
|
return self._discovery
|
||||||
|
|
||||||
@discovery.setter
|
@discovery.setter
|
||||||
def discovery(self, value):
|
def discovery(self, value: Discovery):
|
||||||
"""Set a Discovery object."""
|
"""Set a Discovery object."""
|
||||||
if self._discovery:
|
if self._discovery:
|
||||||
raise RuntimeError("Discovery already set!")
|
raise RuntimeError("Discovery already set!")
|
||||||
self._discovery = value
|
self._discovery = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dbus(self):
|
def dbus(self) -> DBusManager:
|
||||||
"""Return DBusManager object."""
|
"""Return DBusManager object."""
|
||||||
return self._dbus
|
return self._dbus
|
||||||
|
|
||||||
@dbus.setter
|
@dbus.setter
|
||||||
def dbus(self, value):
|
def dbus(self, value: DBusManager):
|
||||||
"""Set a DBusManager object."""
|
"""Set a DBusManager object."""
|
||||||
if self._dbus:
|
if self._dbus:
|
||||||
raise RuntimeError("DBusManager already set!")
|
raise RuntimeError("DBusManager already set!")
|
||||||
self._dbus = value
|
self._dbus = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self) -> HostManager:
|
||||||
"""Return HostManager object."""
|
"""Return HostManager object."""
|
||||||
return self._host
|
return self._host
|
||||||
|
|
||||||
@host.setter
|
@host.setter
|
||||||
def host(self, value):
|
def host(self, value: HostManager):
|
||||||
"""Set a HostManager object."""
|
"""Set a HostManager object."""
|
||||||
if self._host:
|
if self._host:
|
||||||
raise RuntimeError("HostManager already set!")
|
raise RuntimeError("HostManager already set!")
|
||||||
self._host = value
|
self._host = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hassos(self):
|
def ingress(self) -> Ingress:
|
||||||
|
"""Return Ingress object."""
|
||||||
|
return self._ingress
|
||||||
|
|
||||||
|
@ingress.setter
|
||||||
|
def ingress(self, value: Ingress):
|
||||||
|
"""Set a Ingress object."""
|
||||||
|
if self._ingress:
|
||||||
|
raise RuntimeError("Ingress already set!")
|
||||||
|
self._ingress = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hassos(self) -> HassOS:
|
||||||
"""Return HassOS object."""
|
"""Return HassOS object."""
|
||||||
return self._hassos
|
return self._hassos
|
||||||
|
|
||||||
@hassos.setter
|
@hassos.setter
|
||||||
def hassos(self, value):
|
def hassos(self, value: HassOS):
|
||||||
"""Set a HassOS object."""
|
"""Set a HassOS object."""
|
||||||
if self._hassos:
|
if self._hassos:
|
||||||
raise RuntimeError("HassOS already set!")
|
raise RuntimeError("HassOS already set!")
|
||||||
self._hassos = value
|
self._hassos = value
|
||||||
|
|
||||||
def run_in_executor(self, funct, *args):
|
|
||||||
"""Wrapper for executor pool."""
|
|
||||||
return self._loop.run_in_executor(None, funct, *args)
|
|
||||||
|
|
||||||
def create_task(self, coroutine):
|
|
||||||
"""Wrapper for async task."""
|
|
||||||
return self._loop.create_task(coroutine)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreSysAttributes:
|
class CoreSysAttributes:
|
||||||
"""Inheret basic CoreSysAttributes."""
|
"""Inheret basic CoreSysAttributes."""
|
||||||
|
|
||||||
coresys = None
|
coresys = None
|
||||||
|
|
||||||
def __getattr__(self, name):
|
@property
|
||||||
"""Mapping to coresys."""
|
def sys_machine(self) -> str:
|
||||||
if name.startswith("sys_") and hasattr(self.coresys, name[4:]):
|
"""Return running machine type of the Hass.io system."""
|
||||||
return getattr(self.coresys, name[4:])
|
return self.coresys.machine
|
||||||
raise AttributeError(f"Can't resolve {name} on {self}")
|
|
||||||
|
@property
|
||||||
|
def sys_dev(self) -> str:
|
||||||
|
"""Return True if we run dev mode."""
|
||||||
|
return self.coresys.dev
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_timezone(self) -> str:
|
||||||
|
"""Return timezone."""
|
||||||
|
return self.coresys.timezone
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_machine_id(self) -> str:
|
||||||
|
"""Return timezone."""
|
||||||
|
return self.coresys.machine_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_loop(self) -> asyncio.BaseEventLoop:
|
||||||
|
"""Return loop object."""
|
||||||
|
return self.coresys.loop
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_websession(self) -> aiohttp.ClientSession:
|
||||||
|
"""Return websession object."""
|
||||||
|
return self.coresys.websession
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_websession_ssl(self) -> aiohttp.ClientSession:
|
||||||
|
"""Return websession object with disabled SSL."""
|
||||||
|
return self.coresys.websession_ssl
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_config(self) -> CoreConfig:
|
||||||
|
"""Return CoreConfig object."""
|
||||||
|
return self.coresys.config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_hardware(self) -> Hardware:
|
||||||
|
"""Return Hardware object."""
|
||||||
|
return self.coresys.hardware
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_docker(self) -> DockerAPI:
|
||||||
|
"""Return DockerAPI object."""
|
||||||
|
return self.coresys.docker
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_scheduler(self) -> Scheduler:
|
||||||
|
"""Return Scheduler object."""
|
||||||
|
return self.coresys.scheduler
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_dns(self) -> DNSForward:
|
||||||
|
"""Return DNSForward object."""
|
||||||
|
return self.coresys.dns
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_core(self) -> HassIO:
|
||||||
|
"""Return HassIO object."""
|
||||||
|
return self.coresys.core
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_arch(self) -> CpuArch:
|
||||||
|
"""Return CpuArch object."""
|
||||||
|
return self.coresys.arch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_auth(self) -> Auth:
|
||||||
|
"""Return Auth object."""
|
||||||
|
return self.coresys.auth
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_homeassistant(self) -> HomeAssistant:
|
||||||
|
"""Return Home Assistant object."""
|
||||||
|
return self.coresys.homeassistant
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_supervisor(self) -> Supervisor:
|
||||||
|
"""Return Supervisor object."""
|
||||||
|
return self.coresys.supervisor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_api(self) -> RestAPI:
|
||||||
|
"""Return API object."""
|
||||||
|
return self.coresys.api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_updater(self) -> Updater:
|
||||||
|
"""Return Updater object."""
|
||||||
|
return self.coresys.updater
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_addons(self) -> AddonManager:
|
||||||
|
"""Return AddonManager object."""
|
||||||
|
return self.coresys.addons
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_snapshots(self) -> SnapshotManager:
|
||||||
|
"""Return SnapshotManager object."""
|
||||||
|
return self.coresys.snapshots
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_tasks(self) -> Tasks:
|
||||||
|
"""Return Tasks object."""
|
||||||
|
return self.coresys.tasks
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_services(self) -> ServiceManager:
|
||||||
|
"""Return ServiceManager object."""
|
||||||
|
return self.coresys.services
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_discovery(self) -> Discovery:
|
||||||
|
"""Return ServiceManager object."""
|
||||||
|
return self.coresys.discovery
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_dbus(self) -> DBusManager:
|
||||||
|
"""Return DBusManager object."""
|
||||||
|
return self.coresys.dbus
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_host(self) -> HostManager:
|
||||||
|
"""Return HostManager object."""
|
||||||
|
return self.coresys.host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_ingress(self) -> Ingress:
|
||||||
|
"""Return Ingress object."""
|
||||||
|
return self.coresys.ingress
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_hassos(self) -> HassOS:
|
||||||
|
"""Return HassOS object."""
|
||||||
|
return self.coresys.hassos
|
||||||
|
|
||||||
|
def sys_run_in_executor(self, funct, *args) -> asyncio.Future:
|
||||||
|
"""Wrapper for executor pool."""
|
||||||
|
return self.sys_loop.run_in_executor(None, funct, *args)
|
||||||
|
|
||||||
|
def sys_create_task(self, coroutine) -> asyncio.Task:
|
||||||
|
"""Wrapper for async task."""
|
||||||
|
return self.sys_loop.create_task(coroutine)
|
||||||
|
@@ -1,35 +1,50 @@
|
|||||||
"""Handle discover message for Home Assistant."""
|
"""Handle discover message for Home Assistant."""
|
||||||
import logging
|
from __future__ import annotations
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from uuid import uuid4
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
from uuid import uuid4, UUID
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .const import FILE_HASSIO_DISCOVERY, ATTR_CONFIG, ATTR_DISCOVERY
|
from ..const import ATTR_CONFIG, ATTR_DISCOVERY, FILE_HASSIO_DISCOVERY
|
||||||
from .coresys import CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .exceptions import DiscoveryError, HomeAssistantAPIError
|
from ..exceptions import DiscoveryError, HomeAssistantAPIError
|
||||||
from .validate import SCHEMA_DISCOVERY_CONFIG
|
from ..utils.json import JsonConfig
|
||||||
from .utils.json import JsonConfig
|
from .validate import SCHEMA_DISCOVERY_CONFIG, valid_discovery_config
|
||||||
from .services.validate import DISCOVERY_SERVICES
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..addons.addon import Addon
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CMD_NEW = 'post'
|
CMD_NEW = "post"
|
||||||
CMD_DEL = 'delete'
|
CMD_DEL = "delete"
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class Message:
|
||||||
|
"""Represent a single Discovery message."""
|
||||||
|
|
||||||
|
addon: str = attr.ib()
|
||||||
|
service: str = attr.ib()
|
||||||
|
config: Dict[str, Any] = attr.ib(cmp=False)
|
||||||
|
uuid: UUID = attr.ib(factory=lambda: uuid4().hex, cmp=False)
|
||||||
|
|
||||||
|
|
||||||
class Discovery(CoreSysAttributes, JsonConfig):
|
class Discovery(CoreSysAttributes, JsonConfig):
|
||||||
"""Home Assistant Discovery handler."""
|
"""Home Assistant Discovery handler."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize discovery handler."""
|
"""Initialize discovery handler."""
|
||||||
super().__init__(FILE_HASSIO_DISCOVERY, SCHEMA_DISCOVERY_CONFIG)
|
super().__init__(FILE_HASSIO_DISCOVERY, SCHEMA_DISCOVERY_CONFIG)
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.message_obj = {}
|
self.message_obj: Dict[str, Message] = {}
|
||||||
|
|
||||||
async def load(self):
|
async def load(self) -> None:
|
||||||
"""Load exists discovery message into storage."""
|
"""Load exists discovery message into storage."""
|
||||||
messages = {}
|
messages = {}
|
||||||
for message in self._data[ATTR_DISCOVERY]:
|
for message in self._data[ATTR_DISCOVERY]:
|
||||||
@@ -39,9 +54,9 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
_LOGGER.info("Load %d messages", len(messages))
|
_LOGGER.info("Load %d messages", len(messages))
|
||||||
self.message_obj = messages
|
self.message_obj = messages
|
||||||
|
|
||||||
def save(self):
|
def save(self) -> None:
|
||||||
"""Write discovery message into data file."""
|
"""Write discovery message into data file."""
|
||||||
messages = []
|
messages: List[Dict[str, Any]] = []
|
||||||
for message in self.list_messages:
|
for message in self.list_messages:
|
||||||
messages.append(attr.asdict(message))
|
messages.append(attr.asdict(message))
|
||||||
|
|
||||||
@@ -49,22 +64,21 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
self._data[ATTR_DISCOVERY].extend(messages)
|
self._data[ATTR_DISCOVERY].extend(messages)
|
||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def get(self, uuid):
|
def get(self, uuid: str) -> Optional[Message]:
|
||||||
"""Return discovery message."""
|
"""Return discovery message."""
|
||||||
return self.message_obj.get(uuid)
|
return self.message_obj.get(uuid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_messages(self):
|
def list_messages(self) -> List[Message]:
|
||||||
"""Return list of available discovery messages."""
|
"""Return list of available discovery messages."""
|
||||||
return list(self.message_obj.values())
|
return list(self.message_obj.values())
|
||||||
|
|
||||||
def send(self, addon, service, config):
|
def send(self, addon: Addon, service: str, config: Dict[str, Any]) -> Message:
|
||||||
"""Send a discovery message to Home Assistant."""
|
"""Send a discovery message to Home Assistant."""
|
||||||
try:
|
try:
|
||||||
config = DISCOVERY_SERVICES[service](config)
|
config = valid_discovery_config(service, config)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
_LOGGER.error(
|
_LOGGER.error("Invalid discovery %s config", humanize_error(config, err))
|
||||||
"Invalid discovery %s config", humanize_error(config, err))
|
|
||||||
raise DiscoveryError() from None
|
raise DiscoveryError() from None
|
||||||
|
|
||||||
# Create message
|
# Create message
|
||||||
@@ -77,24 +91,26 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
_LOGGER.info("Duplicate discovery message from %s", addon.slug)
|
_LOGGER.info("Duplicate discovery message from %s", addon.slug)
|
||||||
return old_message
|
return old_message
|
||||||
|
|
||||||
_LOGGER.info("Send discovery to Home Assistant %s from %s",
|
_LOGGER.info("Send discovery to Home Assistant %s from %s", service, addon.slug)
|
||||||
service, addon.slug)
|
|
||||||
self.message_obj[message.uuid] = message
|
self.message_obj[message.uuid] = message
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
self.sys_create_task(self._push_discovery(message, CMD_NEW))
|
self.sys_create_task(self._push_discovery(message, CMD_NEW))
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def remove(self, message):
|
def remove(self, message: Message) -> None:
|
||||||
"""Remove a discovery message from Home Assistant."""
|
"""Remove a discovery message from Home Assistant."""
|
||||||
self.message_obj.pop(message.uuid, None)
|
self.message_obj.pop(message.uuid, None)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
_LOGGER.info("Delete discovery to Home Assistant %s from %s",
|
_LOGGER.info(
|
||||||
message.service, message.addon)
|
"Delete discovery to Home Assistant %s from %s",
|
||||||
|
message.service,
|
||||||
|
message.addon,
|
||||||
|
)
|
||||||
self.sys_create_task(self._push_discovery(message, CMD_DEL))
|
self.sys_create_task(self._push_discovery(message, CMD_DEL))
|
||||||
|
|
||||||
async def _push_discovery(self, message, command):
|
async def _push_discovery(self, message: Message, command: str) -> None:
|
||||||
"""Send a discovery request."""
|
"""Send a discovery request."""
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.check_api_state():
|
||||||
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
||||||
@@ -105,18 +121,12 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
|
|
||||||
with suppress(HomeAssistantAPIError):
|
with suppress(HomeAssistantAPIError):
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.make_request(
|
||||||
command, f"api/hassio_push/discovery/{message.uuid}",
|
command,
|
||||||
json=data, timeout=10):
|
f"api/hassio_push/discovery/{message.uuid}",
|
||||||
|
json=data,
|
||||||
|
timeout=10,
|
||||||
|
):
|
||||||
_LOGGER.info("Discovery %s message send", message.uuid)
|
_LOGGER.info("Discovery %s message send", message.uuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.warning("Discovery %s message fail", message.uuid)
|
_LOGGER.warning("Discovery %s message fail", message.uuid)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
|
||||||
class Message:
|
|
||||||
"""Represent a single Discovery message."""
|
|
||||||
addon = attr.ib()
|
|
||||||
service = attr.ib()
|
|
||||||
config = attr.ib(cmp=False)
|
|
||||||
uuid = attr.ib(factory=lambda: uuid4().hex, cmp=False)
|
|
10
hassio/discovery/const.py
Normal file
10
hassio/discovery/const.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""Discovery static data."""
|
||||||
|
|
||||||
|
ATTR_HOST = "host"
|
||||||
|
ATTR_PASSWORD = "password"
|
||||||
|
ATTR_PORT = "port"
|
||||||
|
ATTR_PROTOCOL = "protocol"
|
||||||
|
ATTR_SSL = "ssl"
|
||||||
|
ATTR_USERNAME = "username"
|
||||||
|
ATTR_API_KEY = "api_key"
|
||||||
|
ATTR_SERIAL = "serial"
|
1
hassio/discovery/services/__init__.py
Normal file
1
hassio/discovery/services/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Discovery service modules."""
|
16
hassio/discovery/services/deconz.py
Normal file
16
hassio/discovery/services/deconz.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""Discovery service for MQTT."""
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from hassio.validate import NETWORK_PORT
|
||||||
|
|
||||||
|
from ..const import ATTR_HOST, ATTR_PORT, ATTR_API_KEY, ATTR_SERIAL
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_HOST): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PORT): NETWORK_PORT,
|
||||||
|
vol.Required(ATTR_SERIAL): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_API_KEY): vol.Coerce(str),
|
||||||
|
}
|
||||||
|
)
|
27
hassio/discovery/services/mqtt.py
Normal file
27
hassio/discovery/services/mqtt.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"""Discovery service for MQTT."""
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from hassio.validate import NETWORK_PORT
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_HOST,
|
||||||
|
ATTR_PASSWORD,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_PROTOCOL,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_USERNAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_HOST): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PORT): NETWORK_PORT,
|
||||||
|
vol.Optional(ATTR_USERNAME): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_PASSWORD): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_PROTOCOL, default="3.1.1"): vol.All(
|
||||||
|
vol.Coerce(str), vol.In(["3.1", "3.1.1"])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
47
hassio/discovery/validate.py
Normal file
47
hassio/discovery/validate.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""Validate services schema."""
|
||||||
|
from pathlib import Path
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_SERVICE, ATTR_UUID
|
||||||
|
from ..utils.validate import schema_or
|
||||||
|
from ..validate import UUID_MATCH
|
||||||
|
|
||||||
|
|
||||||
|
def valid_discovery_service(service):
|
||||||
|
"""Validate service name."""
|
||||||
|
service_file = Path(__file__).parent.joinpath(f"services/{service}.py")
|
||||||
|
if not service_file.exists():
|
||||||
|
raise vol.Invalid(f"Service {service} not found")
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
def valid_discovery_config(service, config):
|
||||||
|
"""Validate service name."""
|
||||||
|
try:
|
||||||
|
service_mod = import_module(f".services.{service}", "hassio.discovery")
|
||||||
|
except ImportError:
|
||||||
|
raise vol.Invalid(f"Service {service} not found")
|
||||||
|
|
||||||
|
return service_mod.SCHEMA(config)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY = vol.Schema(
|
||||||
|
[
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_UUID): UUID_MATCH,
|
||||||
|
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_SERVICE): valid_discovery_service,
|
||||||
|
vol.Required(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY_CONFIG = vol.Schema(
|
||||||
|
{vol.Optional(ATTR_DISCOVERY, default=list): schema_or(SCHEMA_DISCOVERY)},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
@@ -1,12 +1,14 @@
|
|||||||
"""Init file for Hass.io Docker object."""
|
"""Init file for Hass.io Docker object."""
|
||||||
from contextlib import suppress
|
|
||||||
import logging
|
import logging
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
from .network import DockerNetwork
|
|
||||||
from ..const import SOCKET_DOCKER
|
from ..const import SOCKET_DOCKER
|
||||||
|
from ..exceptions import DockerAPIError
|
||||||
|
from .network import DockerNetwork
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -14,8 +16,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
@attr.s(frozen=True)
|
@attr.s(frozen=True)
|
||||||
class CommandReturn:
|
class CommandReturn:
|
||||||
"""Return object from command run."""
|
"""Return object from command run."""
|
||||||
exit_code = attr.ib()
|
|
||||||
output = attr.ib()
|
exit_code: int = attr.ib()
|
||||||
|
output: bytes = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class DockerAPI:
|
class DockerAPI:
|
||||||
@@ -26,74 +29,87 @@ class DockerAPI:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize Docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.docker = docker.DockerClient(
|
self.docker: docker.DockerClient = docker.DockerClient(
|
||||||
base_url="unix:/{}".format(str(SOCKET_DOCKER)),
|
base_url="unix:/{}".format(str(SOCKET_DOCKER)), version="auto", timeout=900
|
||||||
version='auto', timeout=900)
|
)
|
||||||
self.network = DockerNetwork(self.docker)
|
self.network: DockerNetwork = DockerNetwork(self.docker)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def images(self):
|
def images(self) -> docker.models.images.ImageCollection:
|
||||||
"""Return API images."""
|
"""Return API images."""
|
||||||
return self.docker.images
|
return self.docker.images
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def containers(self):
|
def containers(self) -> docker.models.containers.ContainerCollection:
|
||||||
"""Return API containers."""
|
"""Return API containers."""
|
||||||
return self.docker.containers
|
return self.docker.containers
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self) -> docker.APIClient:
|
||||||
"""Return API containers."""
|
"""Return API containers."""
|
||||||
return self.docker.api
|
return self.docker.api
|
||||||
|
|
||||||
def run(self, image, **kwargs):
|
def run(
|
||||||
|
self, image: str, **kwargs: Dict[str, Any]
|
||||||
|
) -> docker.models.containers.Container:
|
||||||
""""Create a Docker container and run it.
|
""""Create a Docker container and run it.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
name = kwargs.get('name', image)
|
name = kwargs.get("name", image)
|
||||||
network_mode = kwargs.get('network_mode')
|
network_mode = kwargs.get("network_mode")
|
||||||
hostname = kwargs.get('hostname')
|
hostname = kwargs.get("hostname")
|
||||||
|
|
||||||
# Setup network
|
# Setup network
|
||||||
kwargs['dns_search'] = ["."]
|
kwargs["dns_search"] = ["."]
|
||||||
if network_mode:
|
if network_mode:
|
||||||
kwargs['dns'] = [str(self.network.supervisor)]
|
kwargs["dns"] = [str(self.network.supervisor)]
|
||||||
kwargs['dns_opt'] = ["ndots:0"]
|
kwargs["dns_opt"] = ["ndots:0"]
|
||||||
else:
|
else:
|
||||||
kwargs['network'] = None
|
kwargs["network"] = None
|
||||||
|
|
||||||
# Create container
|
# Create container
|
||||||
try:
|
try:
|
||||||
container = self.docker.containers.create(image, **kwargs)
|
container = self.docker.containers.create(
|
||||||
|
image, use_config_proxy=False, **kwargs
|
||||||
|
)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't create container from %s: %s", name, err)
|
_LOGGER.error("Can't create container from %s: %s", name, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
# attach network
|
# Attach network
|
||||||
if not network_mode:
|
if not network_mode:
|
||||||
alias = [hostname] if hostname else None
|
alias = [hostname] if hostname else None
|
||||||
if self.network.attach_container(container, alias=alias):
|
try:
|
||||||
self.network.detach_default_bridge(container)
|
self.network.attach_container(container, alias=alias)
|
||||||
else:
|
except DockerAPIError:
|
||||||
_LOGGER.warning("Can't attach %s to hassio-net!", name)
|
_LOGGER.warning("Can't attach %s to hassio-net!", name)
|
||||||
|
else:
|
||||||
|
with suppress(DockerAPIError):
|
||||||
|
self.network.detach_default_bridge(container)
|
||||||
|
|
||||||
# run container
|
# Run container
|
||||||
try:
|
try:
|
||||||
container.start()
|
container.start()
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't start %s: %s", name, err)
|
_LOGGER.error("Can't start %s: %s", name, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
return True
|
# Update metadata
|
||||||
|
with suppress(docker.errors.DockerException):
|
||||||
|
container.reload()
|
||||||
|
|
||||||
def run_command(self, image, command=None, **kwargs):
|
return container
|
||||||
|
|
||||||
|
def run_command(
|
||||||
|
self, image: str, command: Optional[str] = None, **kwargs: Dict[str, Any]
|
||||||
|
) -> CommandReturn:
|
||||||
"""Create a temporary container and run command.
|
"""Create a temporary container and run command.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
stdout = kwargs.get('stdout', True)
|
stdout = kwargs.get("stdout", True)
|
||||||
stderr = kwargs.get('stderr', True)
|
stderr = kwargs.get("stderr", True)
|
||||||
|
|
||||||
_LOGGER.info("Run command '%s' on %s", command, image)
|
_LOGGER.info("Run command '%s' on %s", command, image)
|
||||||
try:
|
try:
|
||||||
@@ -101,6 +117,7 @@ class DockerAPI:
|
|||||||
image,
|
image,
|
||||||
command=command,
|
command=command,
|
||||||
network=self.network.name,
|
network=self.network.name,
|
||||||
|
use_config_proxy=False,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,11 +127,11 @@ class DockerAPI:
|
|||||||
|
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't execute command: %s", err)
|
_LOGGER.error("Can't execute command: %s", err)
|
||||||
return CommandReturn(None, b"")
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# cleanup container
|
# cleanup container
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
return CommandReturn(result.get('StatusCode'), output)
|
return CommandReturn(result.get("StatusCode"), output)
|
||||||
|
@@ -1,16 +1,35 @@
|
|||||||
"""Init file for Hass.io add-on Docker object."""
|
"""Init file for Hass.io add-on Docker object."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from contextlib import suppress
|
||||||
|
from ipaddress import IPv4Address, ip_address
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional, Union, Awaitable
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .interface import DockerInterface
|
|
||||||
from ..addons.build import AddonBuild
|
from ..addons.build import AddonBuild
|
||||||
from ..const import (
|
from ..const import (
|
||||||
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
|
ENV_TIME,
|
||||||
ENV_TIME, SECURITY_PROFILE, SECURITY_DISABLE)
|
ENV_TOKEN,
|
||||||
|
MAP_ADDONS,
|
||||||
|
MAP_BACKUP,
|
||||||
|
MAP_CONFIG,
|
||||||
|
MAP_SHARE,
|
||||||
|
MAP_SSL,
|
||||||
|
SECURITY_DISABLE,
|
||||||
|
SECURITY_PROFILE,
|
||||||
|
)
|
||||||
|
from ..coresys import CoreSys
|
||||||
|
from ..exceptions import DockerAPIError
|
||||||
from ..utils import process_lock
|
from ..utils import process_lock
|
||||||
|
from .interface import DockerInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..addons.addon import Addon
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,64 +39,77 @@ AUDIO_DEVICE = "/dev/snd:/dev/snd:rwm"
|
|||||||
class DockerAddon(DockerInterface):
|
class DockerAddon(DockerInterface):
|
||||||
"""Docker Hass.io wrapper for Home Assistant."""
|
"""Docker Hass.io wrapper for Home Assistant."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys: CoreSys, slug: str):
|
||||||
"""Initialize Docker Home Assistant wrapper."""
|
"""Initialize Docker Home Assistant wrapper."""
|
||||||
super().__init__(coresys)
|
super().__init__(coresys)
|
||||||
self._id = slug
|
self._id: str = slug
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addon(self):
|
def addon(self) -> Addon:
|
||||||
"""Return add-on of Docker image."""
|
"""Return add-on of Docker image."""
|
||||||
return self.sys_addons.get(self._id)
|
return self.sys_addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self) -> str:
|
||||||
"""Return name of Docker image."""
|
"""Return name of Docker image."""
|
||||||
return self.addon.image
|
return self.addon.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self):
|
def ip_address(self) -> IPv4Address:
|
||||||
|
"""Return IP address of this container."""
|
||||||
|
if self.addon.host_network:
|
||||||
|
return self.sys_docker.network.gateway
|
||||||
|
|
||||||
|
# Extract IP-Address
|
||||||
|
try:
|
||||||
|
return ip_address(
|
||||||
|
self._meta["NetworkSettings"]["Networks"]["hassio"]["IPAddress"])
|
||||||
|
except (KeyError, TypeError, ValueError):
|
||||||
|
return ip_address("0.0.0.0")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timeout(self) -> int:
|
||||||
"""Return timeout for Docker actions."""
|
"""Return timeout for Docker actions."""
|
||||||
return self.addon.timeout
|
return self.addon.timeout
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self) -> str:
|
||||||
"""Return version of Docker image."""
|
"""Return version of Docker image."""
|
||||||
if not self.addon.legacy:
|
if self.addon.legacy:
|
||||||
return super().version
|
return self.addon.version_installed
|
||||||
return self.addon.version_installed
|
return super().version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self):
|
def arch(self) -> str:
|
||||||
"""Return arch of Docker image."""
|
"""Return arch of Docker image."""
|
||||||
if not self.addon.legacy:
|
if self.addon.legacy:
|
||||||
return super().arch
|
return self.sys_arch.default
|
||||||
return self.sys_arch
|
return super().arch
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return name of Docker container."""
|
"""Return name of Docker container."""
|
||||||
return "addon_{}".format(self.addon.slug)
|
return f"addon_{self.addon.slug}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ipc(self):
|
def ipc(self) -> Optional[str]:
|
||||||
"""Return the IPC namespace."""
|
"""Return the IPC namespace."""
|
||||||
if self.addon.host_ipc:
|
if self.addon.host_ipc:
|
||||||
return 'host'
|
return "host"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_access(self):
|
def full_access(self) -> bool:
|
||||||
"""Return True if full access is enabled."""
|
"""Return True if full access is enabled."""
|
||||||
return not self.addon.protected and self.addon.with_full_access
|
return not self.addon.protected and self.addon.with_full_access
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self):
|
def hostname(self) -> str:
|
||||||
"""Return slug/id of add-on."""
|
"""Return slug/id of add-on."""
|
||||||
return self.addon.slug.replace('_', '-')
|
return self.addon.slug.replace("_", "-")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def environment(self):
|
def environment(self) -> Dict[str, str]:
|
||||||
"""Return environment for Docker add-on."""
|
"""Return environment for Docker add-on."""
|
||||||
addon_env = self.addon.environment or {}
|
addon_env = self.addon.environment or {}
|
||||||
|
|
||||||
@@ -87,8 +119,7 @@ class DockerAddon(DockerInterface):
|
|||||||
if isinstance(value, (int, str)):
|
if isinstance(value, (int, str)):
|
||||||
addon_env[key] = value
|
addon_env[key] = value
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning("Can not set nested option %s as Docker env", key)
|
||||||
"Can not set nested option %s as Docker env", key)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
**addon_env,
|
**addon_env,
|
||||||
@@ -97,7 +128,7 @@ class DockerAddon(DockerInterface):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def devices(self) -> List[str]:
|
||||||
"""Return needed devices."""
|
"""Return needed devices."""
|
||||||
devices = self.addon.devices or []
|
devices = self.addon.devices or []
|
||||||
|
|
||||||
@@ -114,9 +145,9 @@ class DockerAddon(DockerInterface):
|
|||||||
return devices or None
|
return devices or None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self) -> Optional[Dict[str, Union[str, int, None]]]:
|
||||||
"""Filter None from add-on ports."""
|
"""Filter None from add-on ports."""
|
||||||
if not self.addon.ports:
|
if self.addon.host_network or not self.addon.ports:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -126,7 +157,7 @@ class DockerAddon(DockerInterface):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def security_opt(self):
|
def security_opt(self) -> List[str]:
|
||||||
"""Controlling security options."""
|
"""Controlling security options."""
|
||||||
security = []
|
security = []
|
||||||
|
|
||||||
@@ -144,7 +175,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return security
|
return security
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tmpfs(self):
|
def tmpfs(self) -> Optional[Dict[str, str]]:
|
||||||
"""Return tmpfs for Docker add-on."""
|
"""Return tmpfs for Docker add-on."""
|
||||||
options = self.addon.tmpfs
|
options = self.addon.tmpfs
|
||||||
if options:
|
if options:
|
||||||
@@ -152,128 +183,148 @@ class DockerAddon(DockerInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_mapping(self):
|
def network_mapping(self) -> Dict[str, str]:
|
||||||
"""Return hosts mapping."""
|
"""Return hosts mapping."""
|
||||||
return {
|
return {
|
||||||
'homeassistant': self.sys_docker.network.gateway,
|
"homeassistant": self.sys_docker.network.gateway,
|
||||||
'hassio': self.sys_docker.network.supervisor,
|
"hassio": self.sys_docker.network.supervisor,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_mode(self):
|
def network_mode(self) -> Optional[str]:
|
||||||
"""Return network mode for add-on."""
|
"""Return network mode for add-on."""
|
||||||
if self.addon.host_network:
|
if self.addon.host_network:
|
||||||
return 'host'
|
return "host"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pid_mode(self):
|
def pid_mode(self) -> Optional[str]:
|
||||||
"""Return PID mode for add-on."""
|
"""Return PID mode for add-on."""
|
||||||
if not self.addon.protected and self.addon.host_pid:
|
if not self.addon.protected and self.addon.host_pid:
|
||||||
return 'host'
|
return "host"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volumes(self):
|
def volumes(self) -> Dict[str, Dict[str, str]]:
|
||||||
"""Generate volumes for mappings."""
|
"""Generate volumes for mappings."""
|
||||||
volumes = {
|
volumes = {str(self.addon.path_extern_data): {"bind": "/data", "mode": "rw"}}
|
||||||
str(self.addon.path_extern_data): {
|
|
||||||
'bind': "/data", 'mode': 'rw'
|
|
||||||
}}
|
|
||||||
|
|
||||||
addon_mapping = self.addon.map_volumes
|
addon_mapping = self.addon.map_volumes
|
||||||
|
|
||||||
# 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_homeassistant): {
|
{
|
||||||
'bind': "/config", 'mode': addon_mapping[MAP_CONFIG]
|
str(self.sys_config.path_extern_homeassistant): {
|
||||||
}})
|
"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.sys_config.path_extern_ssl): {
|
{
|
||||||
'bind': "/ssl", 'mode': addon_mapping[MAP_SSL]
|
str(self.sys_config.path_extern_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.sys_config.path_extern_addons_local): {
|
{
|
||||||
'bind': "/addons", 'mode': addon_mapping[MAP_ADDONS]
|
str(self.sys_config.path_extern_addons_local): {
|
||||||
}})
|
"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.sys_config.path_extern_backup): {
|
{
|
||||||
'bind': "/backup", 'mode': addon_mapping[MAP_BACKUP]
|
str(self.sys_config.path_extern_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.sys_config.path_extern_share): {
|
{
|
||||||
'bind': "/share", 'mode': addon_mapping[MAP_SHARE]
|
str(self.sys_config.path_extern_share): {
|
||||||
}})
|
"bind": "/share",
|
||||||
|
"mode": addon_mapping[MAP_SHARE],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Init other hardware mappings
|
# Init other hardware mappings
|
||||||
|
|
||||||
# GPIO support
|
# GPIO support
|
||||||
if self.addon.with_gpio and self.sys_hardware.support_gpio:
|
if self.addon.with_gpio and self.sys_hardware.support_gpio:
|
||||||
for gpio_path in ("/sys/class/gpio", "/sys/devices/platform/soc"):
|
for gpio_path in ("/sys/class/gpio", "/sys/devices/platform/soc"):
|
||||||
volumes.update({
|
volumes.update({gpio_path: {"bind": gpio_path, "mode": "rw"}})
|
||||||
gpio_path: {
|
|
||||||
'bind': gpio_path, 'mode': 'rw'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
# DeviceTree support
|
# DeviceTree support
|
||||||
if self.addon.with_devicetree:
|
if self.addon.with_devicetree:
|
||||||
volumes.update({
|
volumes.update(
|
||||||
"/sys/firmware/devicetree/base": {
|
{
|
||||||
'bind': "/device-tree", 'mode': 'ro'
|
"/sys/firmware/devicetree/base": {
|
||||||
},
|
"bind": "/device-tree",
|
||||||
})
|
"mode": "ro",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Kernel Modules support
|
||||||
|
if self.addon.with_kernel_modules:
|
||||||
|
volumes.update({"/lib/modules": {"bind": "/lib/modules", "mode": "ro"}})
|
||||||
|
|
||||||
# Docker API support
|
# Docker API support
|
||||||
if not self.addon.protected and self.addon.access_docker_api:
|
if not self.addon.protected and self.addon.access_docker_api:
|
||||||
volumes.update({
|
volumes.update(
|
||||||
"/var/run/docker.sock": {
|
{"/var/run/docker.sock": {"bind": "/var/run/docker.sock", "mode": "ro"}}
|
||||||
'bind': "/var/run/docker.sock", 'mode': 'ro'
|
)
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
# Host D-Bus system
|
# Host D-Bus system
|
||||||
if self.addon.host_dbus:
|
if self.addon.host_dbus:
|
||||||
volumes.update({
|
volumes.update({"/var/run/dbus": {"bind": "/var/run/dbus", "mode": "rw"}})
|
||||||
"/var/run/dbus": {
|
|
||||||
'bind': "/var/run/dbus", 'mode': 'rw'
|
|
||||||
}})
|
|
||||||
|
|
||||||
# ALSA configuration
|
# ALSA configuration
|
||||||
if self.addon.with_audio:
|
if self.addon.with_audio:
|
||||||
volumes.update({
|
volumes.update(
|
||||||
str(self.addon.path_extern_asound): {
|
{
|
||||||
'bind': "/etc/asound.conf", 'mode': 'ro'
|
str(self.addon.path_extern_asound): {
|
||||||
}})
|
"bind": "/etc/asound.conf",
|
||||||
|
"mode": "ro",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
def _run(self):
|
def _run(self) -> None:
|
||||||
"""Run Docker image.
|
"""Run Docker image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if self._is_running():
|
if self._is_running():
|
||||||
return True
|
return
|
||||||
|
|
||||||
# Security check
|
# Security check
|
||||||
if not self.addon.protected:
|
if not self.addon.protected:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning("%s run with disabled protected mode!", self.addon.name)
|
||||||
"%s run with disabled protected mode!", self.addon.name)
|
|
||||||
|
|
||||||
# cleanup
|
# Cleanup
|
||||||
self._stop()
|
with suppress(DockerAPIError):
|
||||||
|
self._stop()
|
||||||
|
|
||||||
ret = self.sys_docker.run(
|
# Create & Run container
|
||||||
|
docker_container = self.sys_docker.run(
|
||||||
self.image,
|
self.image,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
hostname=self.hostname,
|
hostname=self.hostname,
|
||||||
@@ -291,26 +342,23 @@ class DockerAddon(DockerInterface):
|
|||||||
security_opt=self.security_opt,
|
security_opt=self.security_opt,
|
||||||
environment=self.environment,
|
environment=self.environment,
|
||||||
volumes=self.volumes,
|
volumes=self.volumes,
|
||||||
tmpfs=self.tmpfs
|
tmpfs=self.tmpfs,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ret:
|
_LOGGER.info("Start Docker add-on %s with version %s", self.image, self.version)
|
||||||
_LOGGER.info("Start Docker add-on %s with version %s",
|
self._meta = docker_container.attrs
|
||||||
self.image, self.version)
|
|
||||||
|
|
||||||
return ret
|
def _install(self, tag: str, image: Optional[str] = None) -> None:
|
||||||
|
|
||||||
def _install(self, tag):
|
|
||||||
"""Pull Docker image or build it.
|
"""Pull Docker image or build it.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if self.addon.need_build:
|
if self.addon.need_build:
|
||||||
return self._build(tag)
|
self._build(tag)
|
||||||
|
else:
|
||||||
|
super()._install(tag, image)
|
||||||
|
|
||||||
return super()._install(tag)
|
def _build(self, tag: str) -> None:
|
||||||
|
|
||||||
def _build(self, tag):
|
|
||||||
"""Build a Docker container.
|
"""Build a Docker container.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
@@ -320,27 +368,27 @@ class DockerAddon(DockerInterface):
|
|||||||
_LOGGER.info("Start build %s:%s", self.image, tag)
|
_LOGGER.info("Start build %s:%s", self.image, tag)
|
||||||
try:
|
try:
|
||||||
image, log = self.sys_docker.images.build(
|
image, log = self.sys_docker.images.build(
|
||||||
**build_env.get_docker_args(tag))
|
use_config_proxy=False, **build_env.get_docker_args(tag)
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Build %s:%s done: %s", self.image, tag, log)
|
_LOGGER.debug("Build %s:%s done: %s", self.image, tag, log)
|
||||||
image.tag(self.image, tag='latest')
|
image.tag(self.image, tag="latest")
|
||||||
|
|
||||||
# Update meta data
|
# Update meta data
|
||||||
self._meta = image.attrs
|
self._meta = image.attrs
|
||||||
|
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't build %s:%s: %s", self.image, tag, err)
|
_LOGGER.error("Can't build %s:%s: %s", self.image, tag, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
_LOGGER.info("Build %s:%s done", self.image, tag)
|
_LOGGER.info("Build %s:%s done", self.image, tag)
|
||||||
return True
|
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def export_image(self, path):
|
def export_image(self, tar_file: Path) -> Awaitable[None]:
|
||||||
"""Export current images into a tar file."""
|
"""Export current images into a tar file."""
|
||||||
return self.sys_run_in_executor(self._export_image, path)
|
return self.sys_run_in_executor(self._export_image, tar_file)
|
||||||
|
|
||||||
def _export_image(self, tar_file):
|
def _export_image(self, tar_file: Path) -> None:
|
||||||
"""Export current images into a tar file.
|
"""Export current images into a tar file.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
@@ -349,7 +397,7 @@ class DockerAddon(DockerInterface):
|
|||||||
image = self.sys_docker.api.get_image(self.image)
|
image = self.sys_docker.api.get_image(self.image)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't fetch image %s: %s", self.image, err)
|
_LOGGER.error("Can't fetch image %s: %s", self.image, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
||||||
try:
|
try:
|
||||||
@@ -358,17 +406,16 @@ class DockerAddon(DockerInterface):
|
|||||||
write_tar.write(chunk)
|
write_tar.write(chunk)
|
||||||
except (OSError, requests.exceptions.ReadTimeout) as err:
|
except (OSError, requests.exceptions.ReadTimeout) as err:
|
||||||
_LOGGER.error("Can't write tar file %s: %s", tar_file, err)
|
_LOGGER.error("Can't write tar file %s: %s", tar_file, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
_LOGGER.info("Export image %s done", self.image)
|
_LOGGER.info("Export image %s done", self.image)
|
||||||
return True
|
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def import_image(self, path, tag):
|
def import_image(self, tar_file: Path, tag: str) -> Awaitable[None]:
|
||||||
"""Import a tar file as image."""
|
"""Import a tar file as image."""
|
||||||
return self.sys_run_in_executor(self._import_image, path, tag)
|
return self.sys_run_in_executor(self._import_image, tar_file, tag)
|
||||||
|
|
||||||
def _import_image(self, tar_file, tag):
|
def _import_image(self, tar_file: Path, tag: str) -> None:
|
||||||
"""Import a tar file as image.
|
"""Import a tar file as image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
@@ -377,37 +424,38 @@ class DockerAddon(DockerInterface):
|
|||||||
with tar_file.open("rb") as read_tar:
|
with tar_file.open("rb") as read_tar:
|
||||||
self.sys_docker.api.load_image(read_tar, quiet=True)
|
self.sys_docker.api.load_image(read_tar, quiet=True)
|
||||||
|
|
||||||
image = self.sys_docker.images.get(self.image)
|
docker_image = self.sys_docker.images.get(self.image)
|
||||||
image.tag(self.image, tag=tag)
|
docker_image.tag(self.image, tag=tag)
|
||||||
except (docker.errors.DockerException, OSError) as err:
|
except (docker.errors.DockerException, OSError) as err:
|
||||||
_LOGGER.error("Can't import image %s: %s", self.image, err)
|
_LOGGER.error("Can't import image %s: %s", self.image, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
_LOGGER.info("Import image %s and tag %s", tar_file, tag)
|
_LOGGER.info("Import image %s and tag %s", tar_file, tag)
|
||||||
self._meta = image.attrs
|
self._meta = docker_image.attrs
|
||||||
self._cleanup()
|
|
||||||
return True
|
with suppress(DockerAPIError):
|
||||||
|
self._cleanup()
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def write_stdin(self, data):
|
def write_stdin(self, data: bytes) -> Awaitable[None]:
|
||||||
"""Write to add-on stdin."""
|
"""Write to add-on stdin."""
|
||||||
return self.sys_run_in_executor(self._write_stdin, data)
|
return self.sys_run_in_executor(self._write_stdin, data)
|
||||||
|
|
||||||
def _write_stdin(self, data):
|
def _write_stdin(self, data: bytes) -> None:
|
||||||
"""Write to add-on stdin.
|
"""Write to add-on stdin.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if not self._is_running():
|
if not self._is_running():
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load needed docker objects
|
# Load needed docker objects
|
||||||
container = self.sys_docker.containers.get(self.name)
|
container = self.sys_docker.containers.get(self.name)
|
||||||
socket = container.attach_socket(params={'stdin': 1, 'stream': 1})
|
socket = container.attach_socket(params={"stdin": 1, "stream": 1})
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't attach to %s stdin: %s", self.name, err)
|
_LOGGER.error("Can't attach to %s stdin: %s", self.name, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Write to stdin
|
# Write to stdin
|
||||||
@@ -416,6 +464,4 @@ class DockerAddon(DockerInterface):
|
|||||||
socket.close()
|
socket.close()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Can't write to %s stdin: %s", self.name, err)
|
_LOGGER.error("Can't write to %s stdin: %s", self.name, err)
|
||||||
return False
|
raise DockerAPIError() from None
|
||||||
|
|
||||||
return True
|
|
||||||
|
@@ -3,8 +3,8 @@ import logging
|
|||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
from .interface import DockerInterface
|
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from .interface import DockerInterface
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -15,9 +15,9 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of HassOS CLI image."""
|
"""Return name of HassOS CLI image."""
|
||||||
return f"homeassistant/{self.sys_arch}-hassio-cli"
|
return f"homeassistant/{self.sys_arch.supervisor}-hassio-cli"
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self, remove_container=True):
|
||||||
"""Don't need stop."""
|
"""Don't need stop."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -33,5 +33,6 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
self._meta = image.attrs
|
self._meta = image.attrs
|
||||||
_LOGGER.info("Found HassOS CLI %s with version %s",
|
_LOGGER.info(
|
||||||
self.image, self.version)
|
"Found HassOS CLI %s with version %s", self.image, self.version
|
||||||
|
)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user