mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-18 09:29:34 +00:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8233083392 | ||
![]() |
106378d1d0 | ||
![]() |
01d18d5ff3 | ||
![]() |
6d23f3bd1c | ||
![]() |
ef96579a29 | ||
![]() |
44f0a9f21a | ||
![]() |
d854307acb | ||
![]() |
334b41de71 | ||
![]() |
1da50eab7a | ||
![]() |
b119a42f4d | ||
![]() |
99aa438817 | ||
![]() |
99fa91f480 | ||
![]() |
93969d264d | ||
![]() |
711e199977 | ||
![]() |
4e645332c3 | ||
![]() |
df8afb3337 | ||
![]() |
255a33fc08 | ||
![]() |
d15b6f0294 | ||
![]() |
1aa24e40ae | ||
![]() |
c0bde4a488 | ||
![]() |
2a09b70294 | ||
![]() |
e35b0a54c1 | ||
![]() |
8287330c67 | ||
![]() |
6b16da93cd | ||
![]() |
c1cd9bba45 | ||
![]() |
e33420f26e | ||
![]() |
abd9683e11 | ||
![]() |
8cbeabbe21 | ||
![]() |
df7d988d2f | ||
![]() |
544c009b9c | ||
![]() |
b2e0babc60 | ||
![]() |
f7c79cbd3a | ||
![]() |
587e9618da | ||
![]() |
cb2dd3b81c | ||
![]() |
8d4dd7de3f | ||
![]() |
6927c989d0 | ||
![]() |
97853d1691 | ||
![]() |
0cdef0d118 | ||
![]() |
0b17ffc243 | ||
![]() |
c516d46f16 | ||
![]() |
cb8ec22b6d | ||
![]() |
4a5fbd79c1 | ||
![]() |
b636a03567 | ||
![]() |
c96faf7c0a | ||
![]() |
2e1cd4076a | ||
![]() |
9984a638ba | ||
![]() |
a492bccc03 | ||
![]() |
e7a0e0f565 | ||
![]() |
030e081d45 | ||
![]() |
8537536368 | ||
![]() |
f03f323aac | ||
![]() |
58c0c67796 | ||
![]() |
f5e196a663 | ||
![]() |
808df68e57 | ||
![]() |
fa51c2e6e9 | ||
![]() |
ba3760e770 | ||
![]() |
ad1a8557b8 | ||
![]() |
fe91f812d9 | ||
![]() |
4cc11305c7 | ||
![]() |
898c0330c8 | ||
![]() |
33e5f94f1f | ||
![]() |
da4ee63890 | ||
![]() |
d34203b133 | ||
![]() |
23addfb9a6 | ||
![]() |
81e1227a7b | ||
![]() |
75be8666a6 | ||
![]() |
6031a60084 | ||
![]() |
39d5785118 | ||
![]() |
bddcdcadb2 | ||
![]() |
3eac6a3366 | ||
![]() |
2ecea7c1b4 | ||
![]() |
7cb72b55a8 | ||
![]() |
7a8ee2c46a | ||
![]() |
6e9ef17a28 |
22
API.md
22
API.md
@@ -34,6 +34,7 @@ The addons from `addons` are only installed one.
|
|||||||
"last_version": "LAST_VERSION",
|
"last_version": "LAST_VERSION",
|
||||||
"arch": "armhf|aarch64|i386|amd64",
|
"arch": "armhf|aarch64|i386|amd64",
|
||||||
"beta_channel": "true|false",
|
"beta_channel": "true|false",
|
||||||
|
"timezone": "TIMEZONE",
|
||||||
"addons": [
|
"addons": [
|
||||||
{
|
{
|
||||||
"name": "xy bla",
|
"name": "xy bla",
|
||||||
@@ -98,6 +99,7 @@ Optional:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"beta_channel": "true|false",
|
"beta_channel": "true|false",
|
||||||
|
"timezone": "TIMEZONE",
|
||||||
"addons_repositories": [
|
"addons_repositories": [
|
||||||
"REPO_URL"
|
"REPO_URL"
|
||||||
]
|
]
|
||||||
@@ -176,6 +178,11 @@ Optional:
|
|||||||
### Network
|
### Network
|
||||||
|
|
||||||
- GET `/network/info`
|
- GET `/network/info`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hostname": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- POST `/network/options`
|
- POST `/network/options`
|
||||||
```json
|
```json
|
||||||
@@ -196,7 +203,8 @@ Optional:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"version": "INSTALL_VERSION",
|
"version": "INSTALL_VERSION",
|
||||||
"last_version": "LAST_VERSION"
|
"last_version": "LAST_VERSION",
|
||||||
|
"devices": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -214,6 +222,13 @@ Output the raw docker log
|
|||||||
|
|
||||||
- POST `/homeassistant/restart`
|
- POST `/homeassistant/restart`
|
||||||
|
|
||||||
|
- POST `/homeassistant/options`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"devices": [],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### REST API addons
|
### REST API addons
|
||||||
|
|
||||||
- GET `/addons/{addon}/info`
|
- GET `/addons/{addon}/info`
|
||||||
@@ -281,8 +296,10 @@ Communicate over unix socket with a host daemon.
|
|||||||
# shutdown
|
# shutdown
|
||||||
# host-update [v]
|
# host-update [v]
|
||||||
|
|
||||||
|
# hostname xy
|
||||||
|
|
||||||
# network info
|
# network info
|
||||||
# network hostname xy
|
-> {}
|
||||||
# network wlan ssd xy
|
# network wlan ssd xy
|
||||||
# network wlan password xy
|
# network wlan password xy
|
||||||
# network int ip xy
|
# network int ip xy
|
||||||
@@ -294,6 +311,7 @@ features:
|
|||||||
- shutdown
|
- shutdown
|
||||||
- reboot
|
- reboot
|
||||||
- update
|
- update
|
||||||
|
- hostname
|
||||||
- network_info
|
- network_info
|
||||||
- network_control
|
- network_control
|
||||||
|
|
||||||
|
16
README.md
16
README.md
@@ -9,18 +9,6 @@ Hass.io is a Docker based system for managing your Home Assistant installation a
|
|||||||
|
|
||||||
**HassIO is under active development and is not ready yet for production use.**
|
**HassIO is under active development and is not ready yet for production use.**
|
||||||
|
|
||||||
## Installing Hass.io
|
## Installation
|
||||||
|
|
||||||
Looks to our [website](https://home-assistant.io/hassio).
|
Installation instructions can be found at [https://home-assistant.io/hassio](https://home-assistant.io/hassio).
|
||||||
|
|
||||||
# HomeAssistant
|
|
||||||
|
|
||||||
## SSL
|
|
||||||
|
|
||||||
All addons that create SSL certs follow the same file structure. If you use one, put follow lines in your `configuration.yaml`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
http:
|
|
||||||
ssl_certificate: /ssl/fullchain.pem
|
|
||||||
ssl_key: /ssl/privkey.pem
|
|
||||||
```
|
|
||||||
|
@@ -191,15 +191,13 @@ class AddonManager(AddonsData):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
version = version or self.get_last_version(addon)
|
version = version or self.get_last_version(addon)
|
||||||
is_running = await self.dockers[addon].is_running()
|
|
||||||
|
|
||||||
# update
|
# update
|
||||||
if await self.dockers[addon].update(version):
|
if not await self.dockers[addon].update(version):
|
||||||
self.set_addon_update(addon, version)
|
return False
|
||||||
if is_running:
|
|
||||||
await self.start(addon)
|
self.set_addon_update(addon, version)
|
||||||
return True
|
return True
|
||||||
return False
|
|
||||||
|
|
||||||
async def restart(self, addon):
|
async def restart(self, addon):
|
||||||
"""Restart addon."""
|
"""Restart addon."""
|
||||||
@@ -207,6 +205,10 @@ class AddonManager(AddonsData):
|
|||||||
_LOGGER.error("No docker found for addon %s", addon)
|
_LOGGER.error("No docker found for addon %s", addon)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not self.write_addon_options(addon):
|
||||||
|
_LOGGER.error("Can't write options for addon %s", addon)
|
||||||
|
return False
|
||||||
|
|
||||||
return await self.dockers[addon].restart()
|
return await self.dockers[addon].restart()
|
||||||
|
|
||||||
async def logs(self, addon):
|
async def logs(self, addon):
|
||||||
|
@@ -16,7 +16,8 @@ from ..const import (
|
|||||||
FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON,
|
FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON,
|
||||||
ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO,
|
ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO,
|
||||||
ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY, ATTR_URL, ATTR_ARCH,
|
ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY, ATTR_URL, ATTR_ARCH,
|
||||||
ATTR_LOCATON, ATTR_DEVICES)
|
ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT, ATTR_HOST_NETWORK,
|
||||||
|
ATTR_TMPFS, ATTR_PRIVILEGED)
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from ..tools import read_json_file, write_json_file
|
from ..tools import read_json_file, write_json_file
|
||||||
|
|
||||||
@@ -294,10 +295,28 @@ class AddonsData(Config):
|
|||||||
"""Return ports of addon."""
|
"""Return ports of addon."""
|
||||||
return self._system_data[addon].get(ATTR_PORTS)
|
return self._system_data[addon].get(ATTR_PORTS)
|
||||||
|
|
||||||
|
def get_network_mode(self, addon):
|
||||||
|
"""Return network mode of addon."""
|
||||||
|
if self._system_data[addon][ATTR_HOST_NETWORK]:
|
||||||
|
return 'host'
|
||||||
|
return 'bridge'
|
||||||
|
|
||||||
def get_devices(self, addon):
|
def get_devices(self, addon):
|
||||||
"""Return devices of addon."""
|
"""Return devices of addon."""
|
||||||
return self._system_data[addon].get(ATTR_DEVICES)
|
return self._system_data[addon].get(ATTR_DEVICES)
|
||||||
|
|
||||||
|
def get_tmpfs(self, addon):
|
||||||
|
"""Return tmpfs of addon."""
|
||||||
|
return self._system_data[addon].get(ATTR_TMPFS)
|
||||||
|
|
||||||
|
def get_environment(self, addon):
|
||||||
|
"""Return environment of addon."""
|
||||||
|
return self._system_data[addon].get(ATTR_ENVIRONMENT)
|
||||||
|
|
||||||
|
def get_privileged(self, addon):
|
||||||
|
"""Return list of privilege."""
|
||||||
|
return self._system_data[addon].get(ATTR_PRIVILEGED)
|
||||||
|
|
||||||
def get_url(self, addon):
|
def get_url(self, addon):
|
||||||
"""Return url of addon."""
|
"""Return url of addon."""
|
||||||
if addon in self._addons_cache:
|
if addon in self._addons_cache:
|
||||||
@@ -374,5 +393,7 @@ class AddonsData(Config):
|
|||||||
"""Create a schema for addon options."""
|
"""Create a schema for addon options."""
|
||||||
raw_schema = self._system_data[addon][ATTR_SCHEMA]
|
raw_schema = self._system_data[addon][ATTR_SCHEMA]
|
||||||
|
|
||||||
schema = vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
if isinstance(raw_schema, bool):
|
||||||
return schema
|
return vol.Schema(dict)
|
||||||
|
|
||||||
|
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
||||||
|
@@ -4,12 +4,13 @@ import voluptuous as vol
|
|||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP,
|
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP,
|
||||||
ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER,
|
ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER,
|
||||||
STARTUP_BEFORE, BOOT_AUTO, BOOT_MANUAL, ATTR_SCHEMA, ATTR_IMAGE,
|
STARTUP_BEFORE, STARTUP_INITIALIZE, BOOT_AUTO, BOOT_MANUAL, ATTR_SCHEMA,
|
||||||
ATTR_URL, ATTR_MAINTAINER, ATTR_ARCH, ATTR_DEVICES, ARCH_ARMHF,
|
ATTR_IMAGE, ATTR_URL, ATTR_MAINTAINER, ATTR_ARCH, ATTR_DEVICES,
|
||||||
ARCH_AARCH64, ARCH_AMD64, ARCH_I386)
|
ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64,
|
||||||
|
ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED)
|
||||||
|
|
||||||
|
|
||||||
MAP_VOLUME = r"^(config|ssl|addons|backup)(?::(rw|:ro))?$"
|
MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$"
|
||||||
|
|
||||||
V_STR = 'str'
|
V_STR = 'str'
|
||||||
V_INT = 'int'
|
V_INT = 'int'
|
||||||
@@ -24,8 +25,23 @@ ARCH_ALL = [
|
|||||||
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
|
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PRIVILEGE_ALL = [
|
||||||
|
"NET_ADMIN"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def check_network(data):
|
||||||
|
"""Validate network settings."""
|
||||||
|
host_network = data[ATTR_HOST_NETWORK]
|
||||||
|
|
||||||
|
if ATTR_PORTS in data and host_network:
|
||||||
|
raise vol.Invalid("Hostnetwork & ports are not allow!")
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_ADDON_CONFIG = vol.Schema({
|
SCHEMA_ADDON_CONFIG = vol.Schema(vol.All({
|
||||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||||
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),
|
||||||
@@ -33,20 +49,26 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_URL): vol.Url(),
|
vol.Optional(ATTR_URL): vol.Url(),
|
||||||
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
|
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
|
||||||
vol.Required(ATTR_STARTUP):
|
vol.Required(ATTR_STARTUP):
|
||||||
vol.In([STARTUP_BEFORE, STARTUP_AFTER, STARTUP_ONCE]),
|
vol.In([STARTUP_BEFORE, STARTUP_AFTER, STARTUP_ONCE,
|
||||||
|
STARTUP_INITIALIZE]),
|
||||||
vol.Required(ATTR_BOOT):
|
vol.Required(ATTR_BOOT):
|
||||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||||
vol.Optional(ATTR_PORTS): dict,
|
vol.Optional(ATTR_PORTS): dict,
|
||||||
|
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
|
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
|
||||||
|
vol.Optional(ATTR_TMPFS):
|
||||||
|
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
||||||
vol.Optional(ATTR_MAP, default=[]): [vol.Match(MAP_VOLUME)],
|
vol.Optional(ATTR_MAP, default=[]): [vol.Match(MAP_VOLUME)],
|
||||||
|
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
||||||
|
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGE_ALL)],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
vol.Required(ATTR_SCHEMA): {
|
vol.Required(ATTR_SCHEMA): vol.Any({
|
||||||
vol.Coerce(str): vol.Any(ADDON_ELEMENT, [
|
vol.Coerce(str): vol.Any(ADDON_ELEMENT, [
|
||||||
vol.Any(ADDON_ELEMENT, {vol.Coerce(str): ADDON_ELEMENT})
|
vol.Any(ADDON_ELEMENT, {vol.Coerce(str): ADDON_ELEMENT})
|
||||||
])
|
])
|
||||||
},
|
}, False),
|
||||||
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"),
|
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, check_network), extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
|
@@ -65,6 +65,7 @@ class RestAPI(object):
|
|||||||
api_hass = APIHomeAssistant(self.config, self.loop, dock_homeassistant)
|
api_hass = APIHomeAssistant(self.config, self.loop, dock_homeassistant)
|
||||||
|
|
||||||
self.webapp.router.add_get('/homeassistant/info', api_hass.info)
|
self.webapp.router.add_get('/homeassistant/info', api_hass.info)
|
||||||
|
self.webapp.router.add_post('/homeassistant/options', api_hass.options)
|
||||||
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
||||||
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
||||||
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs)
|
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs)
|
||||||
@@ -99,10 +100,13 @@ class RestAPI(object):
|
|||||||
|
|
||||||
def register_panel(self):
|
def register_panel(self):
|
||||||
"""Register panel for homeassistant."""
|
"""Register panel for homeassistant."""
|
||||||
panel_dir = Path(__file__).parents[1].joinpath('panel')
|
panel = Path(__file__).parents[1].joinpath('panel/hassio-main.html')
|
||||||
|
|
||||||
self.webapp.router.register_resource(
|
def get_panel(request):
|
||||||
web.StaticResource('/panel', str(panel_dir)))
|
"""Return file response with panel."""
|
||||||
|
return web.FileResponse(panel)
|
||||||
|
|
||||||
|
self.webapp.router.add_get('/panel', get_panel)
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Run rest api webserver."""
|
"""Run rest api webserver."""
|
||||||
|
@@ -5,10 +5,15 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .util import api_process, api_process_raw, api_validate
|
from .util import api_process, api_process_raw, api_validate
|
||||||
from ..const import ATTR_VERSION, ATTR_LAST_VERSION
|
from ..const import ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_OPTIONS = vol.Schema({
|
||||||
|
vol.Optional(ATTR_DEVICES): [vol.Match(r"^[^/]*$")],
|
||||||
|
})
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({
|
||||||
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
||||||
})
|
})
|
||||||
@@ -29,8 +34,19 @@ class APIHomeAssistant(object):
|
|||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.homeassistant.version,
|
ATTR_VERSION: self.homeassistant.version,
|
||||||
ATTR_LAST_VERSION: self.config.last_homeassistant,
|
ATTR_LAST_VERSION: self.config.last_homeassistant,
|
||||||
|
ATTR_DEVICES: self.config.homeassistant_devices,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def options(self, request):
|
||||||
|
"""Set homeassistant options."""
|
||||||
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
|
if ATTR_DEVICES in body:
|
||||||
|
self.config.homeassistant_devices = body[ATTR_DEVICES]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request):
|
||||||
"""Update homeassistant."""
|
"""Update homeassistant."""
|
||||||
|
@@ -1,11 +1,19 @@
|
|||||||
"""Init file for HassIO network rest api."""
|
"""Init file for HassIO network rest api."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .util import api_process_hostcontrol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from .util import api_process, api_process_hostcontrol, api_validate
|
||||||
|
from ..const import ATTR_HOSTNAME
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_OPTIONS = vol.Schema({
|
||||||
|
vol.Optional(ATTR_HOSTNAME): vol.Coerce(str),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class APINetwork(object):
|
class APINetwork(object):
|
||||||
"""Handle rest api for network functions."""
|
"""Handle rest api for network functions."""
|
||||||
|
|
||||||
@@ -15,12 +23,21 @@ class APINetwork(object):
|
|||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.host_control = host_control
|
self.host_control = host_control
|
||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process
|
||||||
def info(self, request):
|
async def info(self, request):
|
||||||
"""Show network settings."""
|
"""Show network settings."""
|
||||||
pass
|
return {
|
||||||
|
ATTR_HOSTNAME: self.host_control.hostname,
|
||||||
|
}
|
||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process_hostcontrol
|
||||||
def options(self, request):
|
async def options(self, request):
|
||||||
"""Edit network settings."""
|
"""Edit network settings."""
|
||||||
pass
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
|
# hostname
|
||||||
|
if ATTR_HOSTNAME in body:
|
||||||
|
if self.host_control.hostname != body[ATTR_HOSTNAME]:
|
||||||
|
await self.host_control.set_hostname(body[ATTR_HOSTNAME])
|
||||||
|
|
||||||
|
return True
|
||||||
|
@@ -11,7 +11,8 @@ from ..const import (
|
|||||||
HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES,
|
HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES,
|
||||||
ATTR_REPOSITORY, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED,
|
ATTR_REPOSITORY, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED,
|
||||||
ATTR_DETACHED, ATTR_SOURCE, ATTR_MAINTAINER, ATTR_URL, ATTR_ARCH,
|
ATTR_DETACHED, ATTR_SOURCE, ATTR_MAINTAINER, ATTR_URL, ATTR_ARCH,
|
||||||
ATTR_BUILD)
|
ATTR_BUILD, ATTR_TIMEZONE)
|
||||||
|
from ..tools import validate_timezone
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ SCHEMA_OPTIONS = vol.Schema({
|
|||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(),
|
vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ADDONS_REPOSITORIES): [vol.Url()],
|
vol.Optional(ATTR_ADDONS_REPOSITORIES): [vol.Url()],
|
||||||
|
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
||||||
})
|
})
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({
|
SCHEMA_VERSION = vol.Schema({
|
||||||
@@ -92,6 +94,7 @@ class APISupervisor(object):
|
|||||||
ATTR_LAST_VERSION: self.config.last_hassio,
|
ATTR_LAST_VERSION: self.config.last_hassio,
|
||||||
ATTR_BETA_CHANNEL: self.config.upstream_beta,
|
ATTR_BETA_CHANNEL: self.config.upstream_beta,
|
||||||
ATTR_ARCH: self.addons.arch,
|
ATTR_ARCH: self.addons.arch,
|
||||||
|
ATTR_TIMEZONE: self.config.timezone,
|
||||||
ATTR_ADDONS: self._addons_list(only_installed=True),
|
ATTR_ADDONS: self._addons_list(only_installed=True),
|
||||||
ATTR_ADDONS_REPOSITORIES: self.config.addons_repositories,
|
ATTR_ADDONS_REPOSITORIES: self.config.addons_repositories,
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,9 @@ class APISupervisor(object):
|
|||||||
if ATTR_BETA_CHANNEL in body:
|
if ATTR_BETA_CHANNEL in body:
|
||||||
self.config.upstream_beta = body[ATTR_BETA_CHANNEL]
|
self.config.upstream_beta = body[ATTR_BETA_CHANNEL]
|
||||||
|
|
||||||
|
if ATTR_TIMEZONE in body:
|
||||||
|
self.config.timezone = body[ATTR_TIMEZONE]
|
||||||
|
|
||||||
if ATTR_ADDONS_REPOSITORIES in body:
|
if ATTR_ADDONS_REPOSITORIES in body:
|
||||||
new = set(body[ATTR_ADDONS_REPOSITORIES])
|
new = set(body[ATTR_ADDONS_REPOSITORIES])
|
||||||
old = set(self.config.addons_repositories)
|
old = set(self.config.addons_repositories)
|
||||||
|
@@ -21,24 +21,24 @@ def initialize_system_data(websession):
|
|||||||
"Create Home-Assistant config folder %s", config.path_config)
|
"Create Home-Assistant config folder %s", config.path_config)
|
||||||
config.path_config.mkdir()
|
config.path_config.mkdir()
|
||||||
|
|
||||||
# homeassistant ssl folder
|
# hassio ssl folder
|
||||||
if not config.path_ssl.is_dir():
|
if not config.path_ssl.is_dir():
|
||||||
_LOGGER.info("Create Home-Assistant ssl folder %s", config.path_ssl)
|
_LOGGER.info("Create hassio ssl folder %s", config.path_ssl)
|
||||||
config.path_ssl.mkdir()
|
config.path_ssl.mkdir()
|
||||||
|
|
||||||
# homeassistant 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("Create Home-Assistant addon data folder %s",
|
_LOGGER.info(
|
||||||
config.path_addons_data)
|
"Create hassio addon 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 Home-Assistant addon local repository folder %s",
|
_LOGGER.info("Create hassio addon local repository folder %s",
|
||||||
config.path_addons_local)
|
config.path_addons_local)
|
||||||
config.path_addons_local.mkdir(parents=True)
|
config.path_addons_local.mkdir(parents=True)
|
||||||
|
|
||||||
if not config.path_addons_git.is_dir():
|
if not config.path_addons_git.is_dir():
|
||||||
_LOGGER.info("Create Home-Assistant addon git repositories folder %s",
|
_LOGGER.info("Create hassio addon git repositories folder %s",
|
||||||
config.path_addons_git)
|
config.path_addons_git)
|
||||||
config.path_addons_git.mkdir(parents=True)
|
config.path_addons_git.mkdir(parents=True)
|
||||||
|
|
||||||
@@ -47,12 +47,16 @@ def initialize_system_data(websession):
|
|||||||
config.path_addons_build)
|
config.path_addons_build)
|
||||||
config.path_addons_build.mkdir(parents=True)
|
config.path_addons_build.mkdir(parents=True)
|
||||||
|
|
||||||
# homeassistant backup folder
|
# hassio backup folder
|
||||||
if not config.path_backup.is_dir():
|
if not config.path_backup.is_dir():
|
||||||
_LOGGER.info("Create Home-Assistant backup folder %s",
|
_LOGGER.info("Create hassio backup folder %s", config.path_backup)
|
||||||
config.path_backup)
|
|
||||||
config.path_backup.mkdir()
|
config.path_backup.mkdir()
|
||||||
|
|
||||||
|
# share folder
|
||||||
|
if not config.path_share.is_dir():
|
||||||
|
_LOGGER.info("Create hassio share folder %s", config.path_share)
|
||||||
|
config.path_share.mkdir()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ from voluptuous.humanize import humanize_error
|
|||||||
|
|
||||||
from .const import FILE_HASSIO_CONFIG, HASSIO_SHARE
|
from .const import FILE_HASSIO_CONFIG, HASSIO_SHARE
|
||||||
from .tools import (
|
from .tools import (
|
||||||
fetch_last_versions, write_json_file, read_json_file)
|
fetch_last_versions, write_json_file, read_json_file, validate_timezone)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@ DATETIME_FORMAT = "%Y%m%d %H:%M:%S"
|
|||||||
|
|
||||||
HOMEASSISTANT_CONFIG = PurePath("homeassistant")
|
HOMEASSISTANT_CONFIG = PurePath("homeassistant")
|
||||||
HOMEASSISTANT_LAST = 'homeassistant_last'
|
HOMEASSISTANT_LAST = 'homeassistant_last'
|
||||||
|
HOMEASSISTANT_DEVICES = 'homeassistant_devices'
|
||||||
|
|
||||||
HASSIO_SSL = PurePath("ssl")
|
HASSIO_SSL = PurePath("ssl")
|
||||||
HASSIO_LAST = 'hassio_last'
|
HASSIO_LAST = 'hassio_last'
|
||||||
HASSIO_CLEANUP = 'hassio_cleanup'
|
|
||||||
|
|
||||||
ADDONS_CORE = PurePath("addons/core")
|
ADDONS_CORE = PurePath("addons/core")
|
||||||
ADDONS_LOCAL = PurePath("addons/local")
|
ADDONS_LOCAL = PurePath("addons/local")
|
||||||
@@ -32,9 +32,11 @@ ADDONS_CUSTOM_LIST = 'addons_custom_list'
|
|||||||
|
|
||||||
BACKUP_DATA = PurePath("backup")
|
BACKUP_DATA = PurePath("backup")
|
||||||
|
|
||||||
UPSTREAM_BETA = 'upstream_beta'
|
SHARE_DATA = PurePath("share")
|
||||||
|
|
||||||
|
UPSTREAM_BETA = 'upstream_beta'
|
||||||
API_ENDPOINT = 'api_endpoint'
|
API_ENDPOINT = 'api_endpoint'
|
||||||
|
TIMEZONE = 'timezone'
|
||||||
|
|
||||||
SECURITY_INITIALIZE = 'security_initialize'
|
SECURITY_INITIALIZE = 'security_initialize'
|
||||||
SECURITY_TOTP = 'security_totp'
|
SECURITY_TOTP = 'security_totp'
|
||||||
@@ -46,9 +48,10 @@ SECURITY_SESSIONS = 'security_sessions'
|
|||||||
SCHEMA_CONFIG = vol.Schema({
|
SCHEMA_CONFIG = vol.Schema({
|
||||||
vol.Optional(UPSTREAM_BETA, default=False): vol.Boolean(),
|
vol.Optional(UPSTREAM_BETA, default=False): vol.Boolean(),
|
||||||
vol.Optional(API_ENDPOINT): vol.Coerce(str),
|
vol.Optional(API_ENDPOINT): vol.Coerce(str),
|
||||||
|
vol.Optional(TIMEZONE, default='UTC'): validate_timezone,
|
||||||
vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str),
|
vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str),
|
||||||
|
vol.Optional(HOMEASSISTANT_DEVICES, default=[]): [vol.Coerce(str)],
|
||||||
vol.Optional(HASSIO_LAST): vol.Coerce(str),
|
vol.Optional(HASSIO_LAST): vol.Coerce(str),
|
||||||
vol.Optional(HASSIO_CLEANUP): vol.Coerce(str),
|
|
||||||
vol.Optional(ADDONS_CUSTOM_LIST, default=[]): [vol.Url()],
|
vol.Optional(ADDONS_CUSTOM_LIST, default=[]): [vol.Url()],
|
||||||
vol.Optional(SECURITY_INITIALIZE, default=False): vol.Boolean(),
|
vol.Optional(SECURITY_INITIALIZE, default=False): vol.Boolean(),
|
||||||
vol.Optional(SECURITY_TOTP): vol.Coerce(str),
|
vol.Optional(SECURITY_TOTP): vol.Coerce(str),
|
||||||
@@ -133,19 +136,28 @@ class CoreConfig(Config):
|
|||||||
def upstream_beta(self, value):
|
def upstream_beta(self, value):
|
||||||
"""Set beta upstream mode."""
|
"""Set beta upstream mode."""
|
||||||
self._data[UPSTREAM_BETA] = bool(value)
|
self._data[UPSTREAM_BETA] = bool(value)
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hassio_cleanup(self):
|
def timezone(self):
|
||||||
"""Return Version they need to cleanup."""
|
"""Return system timezone."""
|
||||||
return self._data.get(HASSIO_CLEANUP)
|
return self._data[TIMEZONE]
|
||||||
|
|
||||||
@hassio_cleanup.setter
|
@timezone.setter
|
||||||
def hassio_cleanup(self, version):
|
def timezone(self, value):
|
||||||
"""Set or remove cleanup flag."""
|
"""Set system timezone."""
|
||||||
if version is None:
|
self._data[TIMEZONE] = value
|
||||||
self._data.pop(HASSIO_CLEANUP, None)
|
self.save()
|
||||||
else:
|
|
||||||
self._data[HASSIO_CLEANUP] = version
|
@property
|
||||||
|
def homeassistant_devices(self):
|
||||||
|
"""Return list of special device to map into homeassistant."""
|
||||||
|
return self._data[HOMEASSISTANT_DEVICES]
|
||||||
|
|
||||||
|
@homeassistant_devices.setter
|
||||||
|
def homeassistant_devices(self, value):
|
||||||
|
"""Set list of special device."""
|
||||||
|
self._data[HOMEASSISTANT_DEVICES] = value
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -233,6 +245,16 @@ class CoreConfig(Config):
|
|||||||
"""Return root backup data folder extern for docker."""
|
"""Return root backup data folder extern for docker."""
|
||||||
return PurePath(self.path_extern_hassio, BACKUP_DATA)
|
return PurePath(self.path_extern_hassio, BACKUP_DATA)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_share(self):
|
||||||
|
"""Return root share data folder."""
|
||||||
|
return Path(HASSIO_SHARE, SHARE_DATA)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_extern_share(self):
|
||||||
|
"""Return root share data folder extern for docker."""
|
||||||
|
return PurePath(self.path_extern_hassio, SHARE_DATA)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addons_repositories(self):
|
def addons_repositories(self):
|
||||||
"""Return list of addons custom repositories."""
|
"""Return list of addons custom repositories."""
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Const file for HassIO."""
|
"""Const file for HassIO."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
HASSIO_VERSION = '0.28'
|
HASSIO_VERSION = '0.37'
|
||||||
|
|
||||||
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
||||||
'hassio/master/version.json')
|
'hassio/master/version.json')
|
||||||
@@ -43,6 +43,7 @@ RESULT_OK = 'ok'
|
|||||||
|
|
||||||
ATTR_ARCH = 'arch'
|
ATTR_ARCH = 'arch'
|
||||||
ATTR_HOSTNAME = 'hostname'
|
ATTR_HOSTNAME = 'hostname'
|
||||||
|
ATTR_TIMEZONE = 'timezone'
|
||||||
ATTR_OS = 'os'
|
ATTR_OS = 'os'
|
||||||
ATTR_TYPE = 'type'
|
ATTR_TYPE = 'type'
|
||||||
ATTR_SOURCE = 'source'
|
ATTR_SOURCE = 'source'
|
||||||
@@ -76,7 +77,12 @@ ATTR_SESSION = 'session'
|
|||||||
ATTR_LOCATON = 'location'
|
ATTR_LOCATON = 'location'
|
||||||
ATTR_BUILD = 'build'
|
ATTR_BUILD = 'build'
|
||||||
ATTR_DEVICES = 'devices'
|
ATTR_DEVICES = 'devices'
|
||||||
|
ATTR_ENVIRONMENT = 'environment'
|
||||||
|
ATTR_HOST_NETWORK = 'host_network'
|
||||||
|
ATTR_TMPFS = 'tmpfs'
|
||||||
|
ATTR_PRIVILEGED = 'privileged'
|
||||||
|
|
||||||
|
STARTUP_INITIALIZE = 'initialize'
|
||||||
STARTUP_BEFORE = 'before'
|
STARTUP_BEFORE = 'before'
|
||||||
STARTUP_AFTER = 'after'
|
STARTUP_AFTER = 'after'
|
||||||
STARTUP_ONCE = 'once'
|
STARTUP_ONCE = 'once'
|
||||||
@@ -91,6 +97,7 @@ MAP_CONFIG = 'config'
|
|||||||
MAP_SSL = 'ssl'
|
MAP_SSL = 'ssl'
|
||||||
MAP_ADDONS = 'addons'
|
MAP_ADDONS = 'addons'
|
||||||
MAP_BACKUP = 'backup'
|
MAP_BACKUP = 'backup'
|
||||||
|
MAP_SHARE = 'share'
|
||||||
|
|
||||||
ARCH_ARMHF = 'armhf'
|
ARCH_ARMHF = 'armhf'
|
||||||
ARCH_AARCH64 = 'aarch64'
|
ARCH_AARCH64 = 'aarch64'
|
||||||
|
@@ -12,14 +12,15 @@ from .host_control import HostControl
|
|||||||
from .const import (
|
from .const import (
|
||||||
SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS,
|
SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS,
|
||||||
RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT,
|
RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT,
|
||||||
RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE)
|
RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE,
|
||||||
|
STARTUP_INITIALIZE)
|
||||||
from .scheduler import Scheduler
|
from .scheduler import Scheduler
|
||||||
from .dock.homeassistant import DockerHomeAssistant
|
from .dock.homeassistant import DockerHomeAssistant
|
||||||
from .dock.supervisor import DockerSupervisor
|
from .dock.supervisor import DockerSupervisor
|
||||||
from .tasks import (
|
from .tasks import (
|
||||||
hassio_update, homeassistant_watchdog, homeassistant_setup,
|
hassio_update, homeassistant_watchdog, homeassistant_setup,
|
||||||
api_sessions_cleanup)
|
api_sessions_cleanup)
|
||||||
from .tools import get_arch_from_image, get_local_ip
|
from .tools import get_arch_from_image, get_local_ip, fetch_timezone
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class HassIO(object):
|
|||||||
|
|
||||||
# init basic docker container
|
# init basic docker container
|
||||||
self.supervisor = DockerSupervisor(
|
self.supervisor = DockerSupervisor(
|
||||||
self.config, self.loop, self.dock, self)
|
self.config, self.loop, self.dock, self.stop)
|
||||||
self.homeassistant = DockerHomeAssistant(
|
self.homeassistant = DockerHomeAssistant(
|
||||||
self.config, self.loop, self.dock)
|
self.config, self.loop, self.dock)
|
||||||
|
|
||||||
@@ -53,19 +54,24 @@ class HassIO(object):
|
|||||||
async def setup(self):
|
async def setup(self):
|
||||||
"""Setup HassIO orchestration."""
|
"""Setup HassIO orchestration."""
|
||||||
# supervisor
|
# supervisor
|
||||||
await self.supervisor.attach()
|
if not await self.supervisor.attach():
|
||||||
|
_LOGGER.fatal("Can't attach to supervisor docker container!")
|
||||||
await self.supervisor.cleanup()
|
await self.supervisor.cleanup()
|
||||||
|
|
||||||
# set api endpoint
|
# set api endpoint
|
||||||
self.config.api_endpoint = await get_local_ip(self.loop)
|
self.config.api_endpoint = await get_local_ip(self.loop)
|
||||||
|
|
||||||
|
# update timezone
|
||||||
|
if self.config.timezone == 'UTC':
|
||||||
|
self.config.timezone = await fetch_timezone(self.websession)
|
||||||
|
|
||||||
# hostcontrol
|
# hostcontrol
|
||||||
await self.host_control.load()
|
await self.host_control.load()
|
||||||
|
|
||||||
# schedule update info tasks
|
# schedule update info tasks
|
||||||
self.scheduler.register_task(
|
self.scheduler.register_task(
|
||||||
self.host_control.load, RUN_UPDATE_INFO_TASKS)
|
|
||||||
|
|
||||||
|
self.host_control.load, RUN_UPDATE_INFO_TASKS)
|
||||||
# rest api views
|
# rest api views
|
||||||
self.api.register_host(self.host_control)
|
self.api.register_host(self.host_control)
|
||||||
self.api.register_network(self.host_control)
|
self.api.register_network(self.host_control)
|
||||||
@@ -91,6 +97,8 @@ class HassIO(object):
|
|||||||
_LOGGER.info("No HomeAssistant docker found.")
|
_LOGGER.info("No HomeAssistant docker found.")
|
||||||
await homeassistant_setup(
|
await homeassistant_setup(
|
||||||
self.config, self.loop, self.homeassistant)
|
self.config, self.loop, self.homeassistant)
|
||||||
|
else:
|
||||||
|
await self.homeassistant.attach()
|
||||||
|
|
||||||
# Load addons
|
# Load addons
|
||||||
arch = get_arch_from_image(self.supervisor.image)
|
arch = get_arch_from_image(self.supervisor.image)
|
||||||
@@ -105,6 +113,9 @@ class HassIO(object):
|
|||||||
hassio_update(self.config, self.supervisor),
|
hassio_update(self.config, self.supervisor),
|
||||||
RUN_UPDATE_SUPERVISOR_TASKS)
|
RUN_UPDATE_SUPERVISOR_TASKS)
|
||||||
|
|
||||||
|
# start addon mark as initialize
|
||||||
|
await self.addons.auto_boot(STARTUP_INITIALIZE)
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Start HassIO orchestration."""
|
"""Start HassIO orchestration."""
|
||||||
# start api
|
# start api
|
||||||
|
@@ -6,7 +6,6 @@ import logging
|
|||||||
import docker
|
import docker
|
||||||
|
|
||||||
from ..const import LABEL_VERSION
|
from ..const import LABEL_VERSION
|
||||||
from ..tools import get_version_from_env
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,12 +19,11 @@ class DockerBase(object):
|
|||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.dock = dock
|
self.dock = dock
|
||||||
self.image = image
|
self.image = image
|
||||||
self.container = None
|
|
||||||
self.version = None
|
self.version = None
|
||||||
self._lock = asyncio.Lock(loop=loop)
|
self._lock = asyncio.Lock(loop=loop)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker_name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -34,18 +32,18 @@ class DockerBase(object):
|
|||||||
"""Return True if a task is in progress."""
|
"""Return True if a task is in progress."""
|
||||||
return self._lock.locked()
|
return self._lock.locked()
|
||||||
|
|
||||||
def process_metadata(self, metadata=None, force=False):
|
def process_metadata(self, metadata, force=False):
|
||||||
"""Read metadata and set it to object."""
|
"""Read metadata and set it to object."""
|
||||||
if not force and self.version:
|
# read image
|
||||||
return
|
if not self.image:
|
||||||
|
self.image = metadata['Config']['Image']
|
||||||
|
|
||||||
# read metadata
|
# read metadata
|
||||||
metadata = metadata or self.container.attrs
|
need_version = force or not self.version
|
||||||
if LABEL_VERSION in metadata['Config']['Labels']:
|
if need_version and LABEL_VERSION in metadata['Config']['Labels']:
|
||||||
self.version = metadata['Config']['Labels'][LABEL_VERSION]
|
self.version = metadata['Config']['Labels'][LABEL_VERSION]
|
||||||
else:
|
elif need_version:
|
||||||
# dedicated
|
_LOGGER.warning("Can't read version from %s", self.name)
|
||||||
self.version = get_version_from_env(metadata['Config']['Env'])
|
|
||||||
|
|
||||||
async def install(self, tag):
|
async def install(self, tag):
|
||||||
"""Pull docker image."""
|
"""Pull docker image."""
|
||||||
@@ -66,7 +64,7 @@ class DockerBase(object):
|
|||||||
image = self.dock.images.pull("{}:{}".format(self.image, tag))
|
image = self.dock.images.pull("{}:{}".format(self.image, tag))
|
||||||
|
|
||||||
image.tag(self.image, tag='latest')
|
image.tag(self.image, tag='latest')
|
||||||
self.process_metadata(metadata=image.attrs, force=True)
|
self.process_metadata(image.attrs, force=True)
|
||||||
except docker.errors.APIError as err:
|
except docker.errors.APIError as err:
|
||||||
_LOGGER.error("Can't install %s:%s -> %s.", self.image, tag, err)
|
_LOGGER.error("Can't install %s:%s -> %s.", self.image, tag, err)
|
||||||
return False
|
return False
|
||||||
@@ -87,8 +85,7 @@ class DockerBase(object):
|
|||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
image = self.dock.images.get(self.image)
|
self.dock.images.get(self.image)
|
||||||
self.process_metadata(metadata=image.attrs)
|
|
||||||
except docker.errors.DockerException:
|
except docker.errors.DockerException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -106,16 +103,21 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if not self.container:
|
try:
|
||||||
try:
|
container = self.dock.containers.get(self.name)
|
||||||
self.container = self.dock.containers.get(self.docker_name)
|
image = self.dock.images.get(self.image)
|
||||||
self.process_metadata()
|
except docker.errors.DockerException:
|
||||||
except docker.errors.DockerException:
|
return False
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.container.reload()
|
|
||||||
|
|
||||||
return self.container.status == 'running'
|
# container is not running
|
||||||
|
if container.status != 'running':
|
||||||
|
return False
|
||||||
|
|
||||||
|
# we run on a old image, stop and start it
|
||||||
|
if container.image.id != image.id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
async def attach(self):
|
async def attach(self):
|
||||||
"""Attach to running docker container."""
|
"""Attach to running docker container."""
|
||||||
@@ -132,16 +134,17 @@ class DockerBase(object):
|
|||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.container = self.dock.containers.get(self.docker_name)
|
if self.image:
|
||||||
self.image = self.container.attrs['Config']['Image']
|
obj_data = self.dock.images.get(self.image).attrs
|
||||||
self.process_metadata()
|
else:
|
||||||
_LOGGER.info("Attach to image %s with version %s",
|
obj_data = self.dock.containers.get(self.name).attrs
|
||||||
self.image, self.version)
|
except docker.errors.DockerException:
|
||||||
except (docker.errors.DockerException, KeyError):
|
|
||||||
_LOGGER.fatal(
|
|
||||||
"Can't attach to %s docker container!", self.docker_name)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.process_metadata(obj_data)
|
||||||
|
_LOGGER.info(
|
||||||
|
"Attach to image %s with version %s", self.image, self.version)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
@@ -175,20 +178,19 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if not self.container:
|
try:
|
||||||
|
container = self.dock.containers.get(self.name)
|
||||||
|
except docker.errors.DockerException:
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info("Stop %s docker application", self.image)
|
_LOGGER.info("Stop %s docker application", self.image)
|
||||||
|
|
||||||
self.container.reload()
|
if container.status == 'running':
|
||||||
if self.container.status == 'running':
|
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
self.container.stop()
|
container.stop()
|
||||||
|
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
self.container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
self.container = None
|
|
||||||
|
|
||||||
async def remove(self):
|
async def remove(self):
|
||||||
"""Remove docker container."""
|
"""Remove docker container."""
|
||||||
@@ -204,11 +206,11 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if self._is_running():
|
# cleanup container
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
_LOGGER.info("Remove docker %s with latest and %s",
|
_LOGGER.info(
|
||||||
self.image, self.version)
|
"Remove docker %s with latest and %s", self.image, self.version)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with suppress(docker.errors.ImageNotFound):
|
with suppress(docker.errors.ImageNotFound):
|
||||||
@@ -239,23 +241,21 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
old_image = "{}:{}".format(self.image, self.version)
|
was_running = self._is_running()
|
||||||
|
|
||||||
_LOGGER.info("Update docker %s with %s:%s",
|
_LOGGER.info(
|
||||||
old_image, self.image, tag)
|
"Update docker %s with %s:%s", self.version, self.image, tag)
|
||||||
|
|
||||||
# update docker image
|
# update docker image
|
||||||
if self._install(tag):
|
if not self._install(tag):
|
||||||
_LOGGER.info("Cleanup old %s docker", old_image)
|
return False
|
||||||
self._stop()
|
|
||||||
try:
|
|
||||||
self.dock.images.remove(image=old_image, force=True)
|
|
||||||
except docker.errors.DockerException as err:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Can't remove old image %s -> %s", old_image, err)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
# cleanup old stuff
|
||||||
|
if was_running:
|
||||||
|
self._run()
|
||||||
|
self._cleanup()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
async def logs(self):
|
async def logs(self):
|
||||||
"""Return docker logs of container."""
|
"""Return docker logs of container."""
|
||||||
@@ -271,11 +271,13 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if not self.container:
|
try:
|
||||||
return
|
container = self.dock.containers.get(self.name)
|
||||||
|
except docker.errors.DockerException:
|
||||||
|
return b""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.container.logs(tail=100, stdout=True, stderr=True)
|
return container.logs(tail=100, stdout=True, stderr=True)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.warning("Can't grap logs from %s -> %s", self.image, err)
|
_LOGGER.warning("Can't grap logs from %s -> %s", self.image, err)
|
||||||
|
|
||||||
@@ -293,15 +295,45 @@ class DockerBase(object):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
if not self.container:
|
try:
|
||||||
|
container = self.dock.containers.get(self.name)
|
||||||
|
except docker.errors.DockerException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_LOGGER.info("Restart %s", self.image)
|
_LOGGER.info("Restart %s", self.image)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.container.restart(timeout=30)
|
container.restart(timeout=30)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.warning("Can't restart %s -> %s", self.image, err)
|
_LOGGER.warning("Can't restart %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Check if old version exists and cleanup."""
|
||||||
|
if self._lock.locked():
|
||||||
|
_LOGGER.error("Can't excute cleanup while a task is in progress")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
await self.loop.run_in_executor(None, self._cleanup)
|
||||||
|
|
||||||
|
def _cleanup(self):
|
||||||
|
"""Check if old version exists and cleanup.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
latest = self.dock.images.get(self.image)
|
||||||
|
except docker.errors.DockerException:
|
||||||
|
_LOGGER.warning("Can't find %s for cleanup", self.image)
|
||||||
|
return
|
||||||
|
|
||||||
|
for image in self.dock.images.list(name=self.image):
|
||||||
|
if latest.id == image.id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with suppress(docker.errors.DockerException):
|
||||||
|
_LOGGER.info("Cleanup docker images: %s", image.tags)
|
||||||
|
self.dock.images.remove(image.id, force=True)
|
||||||
|
@@ -7,7 +7,8 @@ import docker
|
|||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
from .util import dockerfile_template
|
from .util import dockerfile_template
|
||||||
from ..const import META_ADDON, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP
|
from ..const import (
|
||||||
|
META_ADDON, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -23,10 +24,28 @@ class DockerAddon(DockerBase):
|
|||||||
self.addons_data = addons_data
|
self.addons_data = addons_data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker_name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return "addon_{}".format(self.addon)
|
return "addon_{}".format(self.addon)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def environment(self):
|
||||||
|
"""Return environment for docker add-on."""
|
||||||
|
addon_env = self.addons_data.get_environment(self.addon) or {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
**addon_env,
|
||||||
|
'TZ': self.config.timezone,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tmpfs(self):
|
||||||
|
"""Return tmpfs for docker add-on."""
|
||||||
|
options = self.addons_data.get_tmpfs(self.addon)
|
||||||
|
if options:
|
||||||
|
return {"/tmpfs": "{}".format(options)}
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volumes(self):
|
def volumes(self):
|
||||||
"""Generate volumes for mappings."""
|
"""Generate volumes for mappings."""
|
||||||
@@ -61,6 +80,12 @@ class DockerAddon(DockerBase):
|
|||||||
'bind': '/backup', 'mode': addon_mapping[MAP_BACKUP]
|
'bind': '/backup', 'mode': addon_mapping[MAP_BACKUP]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
|
if MAP_SHARE in addon_mapping:
|
||||||
|
volumes.update({
|
||||||
|
str(self.config.path_extern_share): {
|
||||||
|
'bind': '/share', 'mode': addon_mapping[MAP_SHARE]
|
||||||
|
}})
|
||||||
|
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
@@ -71,56 +96,31 @@ class DockerAddon(DockerBase):
|
|||||||
if self._is_running():
|
if self._is_running():
|
||||||
return
|
return
|
||||||
|
|
||||||
# cleanup old container
|
# cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.container = self.dock.containers.run(
|
self.dock.containers.run(
|
||||||
self.image,
|
self.image,
|
||||||
name=self.docker_name,
|
name=self.name,
|
||||||
detach=True,
|
detach=True,
|
||||||
network_mode='bridge',
|
network_mode=self.addons_data.get_network_mode(self.addon),
|
||||||
ports=self.addons_data.get_ports(self.addon),
|
ports=self.addons_data.get_ports(self.addon),
|
||||||
devices=self.addons_data.get_devices(self.addon),
|
devices=self.addons_data.get_devices(self.addon),
|
||||||
|
cap_add=self.addons_data.get_privileged(self.addon),
|
||||||
|
environment=self.environment,
|
||||||
volumes=self.volumes,
|
volumes=self.volumes,
|
||||||
|
tmpfs=self.tmpfs
|
||||||
)
|
)
|
||||||
|
|
||||||
self.process_metadata()
|
|
||||||
_LOGGER.info("Start docker addon %s with version %s",
|
|
||||||
self.image, self.version)
|
|
||||||
|
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Start docker addon %s with version %s", self.image, self.version)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _attach(self):
|
|
||||||
"""Attach to running docker container.
|
|
||||||
|
|
||||||
Need run inside executor.
|
|
||||||
"""
|
|
||||||
# read container
|
|
||||||
try:
|
|
||||||
self.container = self.dock.containers.get(self.docker_name)
|
|
||||||
self.process_metadata()
|
|
||||||
|
|
||||||
_LOGGER.info("Attach to container %s with version %s",
|
|
||||||
self.image, self.version)
|
|
||||||
return
|
|
||||||
except (docker.errors.DockerException, KeyError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# read image
|
|
||||||
try:
|
|
||||||
image = self.dock.images.get(self.image)
|
|
||||||
self.process_metadata(metadata=image.attrs)
|
|
||||||
|
|
||||||
_LOGGER.info("Attach to image %s with version %s",
|
|
||||||
self.image, self.version)
|
|
||||||
except (docker.errors.DockerException, KeyError):
|
|
||||||
_LOGGER.error("No container/image found for %s", self.image)
|
|
||||||
|
|
||||||
def _install(self, tag):
|
def _install(self, tag):
|
||||||
"""Pull docker image or build it.
|
"""Pull docker image or build it.
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ class DockerAddon(DockerBase):
|
|||||||
path=str(build_dir), tag=build_tag, pull=True)
|
path=str(build_dir), tag=build_tag, pull=True)
|
||||||
|
|
||||||
image.tag(self.image, tag='latest')
|
image.tag(self.image, tag='latest')
|
||||||
self.process_metadata(metadata=image.attrs, force=True)
|
self.process_metadata(image.attrs, force=True)
|
||||||
|
|
||||||
except (docker.errors.DockerException, TypeError) as err:
|
except (docker.errors.DockerException, TypeError) as err:
|
||||||
_LOGGER.error("Can't build %s -> %s", build_tag, err)
|
_LOGGER.error("Can't build %s -> %s", build_tag, err)
|
||||||
|
@@ -18,10 +18,22 @@ class DockerHomeAssistant(DockerBase):
|
|||||||
super().__init__(config, loop, dock, image=config.homeassistant_image)
|
super().__init__(config, loop, dock, image=config.homeassistant_image)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker_name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return HASS_DOCKER_NAME
|
return HASS_DOCKER_NAME
|
||||||
|
|
||||||
|
@property
|
||||||
|
def devices(self):
|
||||||
|
"""Create list of special device to map into docker."""
|
||||||
|
if not self.config.homeassistant_devices:
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = []
|
||||||
|
for device in self.config.homeassistant_devices:
|
||||||
|
devices.append("/dev/{0}:/dev/{0}:rwm".format(device))
|
||||||
|
|
||||||
|
return devices
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""Run docker image.
|
"""Run docker image.
|
||||||
|
|
||||||
@@ -30,46 +42,34 @@ class DockerHomeAssistant(DockerBase):
|
|||||||
if self._is_running():
|
if self._is_running():
|
||||||
return
|
return
|
||||||
|
|
||||||
# cleanup old container
|
# cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.container = self.dock.containers.run(
|
self.dock.containers.run(
|
||||||
self.image,
|
self.image,
|
||||||
name=self.docker_name,
|
name=self.name,
|
||||||
detach=True,
|
detach=True,
|
||||||
privileged=True,
|
privileged=True,
|
||||||
|
devices=self.devices,
|
||||||
network_mode='host',
|
network_mode='host',
|
||||||
environment={
|
environment={
|
||||||
'HASSIO': self.config.api_endpoint,
|
'HASSIO': self.config.api_endpoint,
|
||||||
|
'TZ': self.config.timezone,
|
||||||
},
|
},
|
||||||
volumes={
|
volumes={
|
||||||
str(self.config.path_extern_config):
|
str(self.config.path_extern_config):
|
||||||
{'bind': '/config', 'mode': 'rw'},
|
{'bind': '/config', 'mode': 'rw'},
|
||||||
str(self.config.path_extern_ssl):
|
str(self.config.path_extern_ssl):
|
||||||
{'bind': '/ssl', 'mode': 'rw'},
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
|
str(self.config.path_extern_share):
|
||||||
|
{'bind': '/share', 'mode': 'rw'},
|
||||||
})
|
})
|
||||||
|
|
||||||
self.process_metadata()
|
|
||||||
|
|
||||||
_LOGGER.info("Start docker addon %s with version %s",
|
|
||||||
self.image, self.version)
|
|
||||||
|
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
_LOGGER.error("Can't run %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Start homeassistant %s with version %s", self.image, self.version)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def update(self, tag):
|
|
||||||
"""Update homeassistant docker image."""
|
|
||||||
if self._lock.locked():
|
|
||||||
_LOGGER.error("Can't excute update while a task is in progress")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
if await self.loop.run_in_executor(None, self._update, tag):
|
|
||||||
await self.loop.run_in_executor(None, self._run)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import docker
|
|
||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
from ..const import RESTART_EXIT_CODE
|
from ..const import RESTART_EXIT_CODE
|
||||||
|
|
||||||
@@ -13,14 +11,13 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class DockerSupervisor(DockerBase):
|
class DockerSupervisor(DockerBase):
|
||||||
"""Docker hassio wrapper for HomeAssistant."""
|
"""Docker hassio wrapper for HomeAssistant."""
|
||||||
|
|
||||||
def __init__(self, config, loop, dock, hassio, image=None):
|
def __init__(self, config, loop, dock, stop_callback, image=None):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize docker base wrapper."""
|
||||||
super().__init__(config, loop, dock, image=image)
|
super().__init__(config, loop, dock, image=image)
|
||||||
|
self.stop_callback = stop_callback
|
||||||
self.hassio = hassio
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker_name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of docker container."""
|
||||||
return os.environ['SUPERVISOR_NAME']
|
return os.environ['SUPERVISOR_NAME']
|
||||||
|
|
||||||
@@ -31,41 +28,14 @@ class DockerSupervisor(DockerBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
_LOGGER.info("Update supervisor docker to %s:%s", self.image, tag)
|
_LOGGER.info("Update supervisor docker to %s:%s", self.image, tag)
|
||||||
old_version = self.version
|
|
||||||
|
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
if await self.loop.run_in_executor(None, self._install, tag):
|
if await self.loop.run_in_executor(None, self._install, tag):
|
||||||
self.config.hassio_cleanup = old_version
|
self.loop.create_task(self.stop_callback(RESTART_EXIT_CODE))
|
||||||
self.loop.create_task(self.hassio.stop(RESTART_EXIT_CODE))
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def cleanup(self):
|
|
||||||
"""Check if old supervisor version exists and cleanup."""
|
|
||||||
if not self.config.hassio_cleanup:
|
|
||||||
return
|
|
||||||
|
|
||||||
async with self._lock:
|
|
||||||
if await self.loop.run_in_executor(None, self._cleanup):
|
|
||||||
self.config.hassio_cleanup = None
|
|
||||||
|
|
||||||
def _cleanup(self):
|
|
||||||
"""Remove old image.
|
|
||||||
|
|
||||||
Need run inside executor.
|
|
||||||
"""
|
|
||||||
old_image = "{}:{}".format(self.image, self.config.hassio_cleanup)
|
|
||||||
|
|
||||||
_LOGGER.info("Old supervisor docker found %s", old_image)
|
|
||||||
try:
|
|
||||||
self.dock.images.remove(image=old_image, force=True)
|
|
||||||
except docker.errors.DockerException as err:
|
|
||||||
_LOGGER.warning("Can't remove old image %s -> %s", old_image, err)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Run docker image."""
|
"""Run docker image."""
|
||||||
raise RuntimeError("Not support on supervisor docker container!")
|
raise RuntimeError("Not support on supervisor docker container!")
|
||||||
|
@@ -5,10 +5,10 @@ from ..const import ARCH_AARCH64, ARCH_ARMHF, ARCH_I386, ARCH_AMD64
|
|||||||
|
|
||||||
|
|
||||||
RESIN_BASE_IMAGE = {
|
RESIN_BASE_IMAGE = {
|
||||||
ARCH_ARMHF: "resin/armhf-alpine:3.5",
|
ARCH_ARMHF: "homeassistant/armhf-base:latest",
|
||||||
ARCH_AARCH64: "resin/aarch64-alpine:3.5",
|
ARCH_AARCH64: "homeassistant/aarch64-base:latest",
|
||||||
ARCH_I386: "resin/i386-alpine:3.5",
|
ARCH_I386: "homeassistant/i386-base:latest",
|
||||||
ARCH_AMD64: "resin/amd64-alpine:3.5",
|
ARCH_AMD64: "homeassistant/amd64-base:latest",
|
||||||
}
|
}
|
||||||
|
|
||||||
TMPL_IMAGE = re.compile(r"%%BASE_IMAGE%%")
|
TMPL_IMAGE = re.compile(r"%%BASE_IMAGE%%")
|
||||||
|
@@ -17,6 +17,7 @@ UNKNOWN = 'unknown'
|
|||||||
FEATURES_SHUTDOWN = 'shutdown'
|
FEATURES_SHUTDOWN = 'shutdown'
|
||||||
FEATURES_REBOOT = 'reboot'
|
FEATURES_REBOOT = 'reboot'
|
||||||
FEATURES_UPDATE = 'update'
|
FEATURES_UPDATE = 'update'
|
||||||
|
FEATURES_HOSTNAME = 'hostname'
|
||||||
FEATURES_NETWORK_INFO = 'network_info'
|
FEATURES_NETWORK_INFO = 'network_info'
|
||||||
FEATURES_NETWORK_CONTROL = 'network_control'
|
FEATURES_NETWORK_CONTROL = 'network_control'
|
||||||
|
|
||||||
@@ -117,3 +118,7 @@ class HostControl(object):
|
|||||||
if version:
|
if version:
|
||||||
return self._send_command("update {}".format(version))
|
return self._send_command("update {}".format(version))
|
||||||
return self._send_command("update")
|
return self._send_command("update")
|
||||||
|
|
||||||
|
def set_hostname(self, hostname):
|
||||||
|
"""Update hostname on host."""
|
||||||
|
return self._send_command("hostname {}".format(hostname))
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,5 +1,6 @@
|
|||||||
"""Tools file for HassIO."""
|
"""Tools file for HassIO."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from contextlib import suppress
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
@@ -7,11 +8,15 @@ import socket
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
import pytz
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from .const import URL_HASSIO_VERSION, URL_HASSIO_VERSION_BETA
|
from .const import URL_HASSIO_VERSION, URL_HASSIO_VERSION_BETA
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FREEGEOIP_URL = "https://freegeoip.io/json/"
|
||||||
|
|
||||||
_RE_VERSION = re.compile(r"VERSION=(.*)")
|
_RE_VERSION = re.compile(r"VERSION=(.*)")
|
||||||
_IMAGE_ARCH = re.compile(r".*/([a-z0-9]*)-hassio-supervisor")
|
_IMAGE_ARCH = re.compile(r".*/([a-z0-9]*)-hassio-supervisor")
|
||||||
|
|
||||||
@@ -41,17 +46,6 @@ def get_arch_from_image(image):
|
|||||||
return found.group(1)
|
return found.group(1)
|
||||||
|
|
||||||
|
|
||||||
def get_version_from_env(env_list):
|
|
||||||
"""Extract Version from ENV list."""
|
|
||||||
for env in env_list:
|
|
||||||
found = _RE_VERSION.match(env)
|
|
||||||
if found:
|
|
||||||
return found.group(1)
|
|
||||||
|
|
||||||
_LOGGER.error("Can't find VERSION in env")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip(loop):
|
def get_local_ip(loop):
|
||||||
"""Retrieve local IP address.
|
"""Retrieve local IP address.
|
||||||
|
|
||||||
@@ -90,3 +84,28 @@ def read_json_file(jsonfile):
|
|||||||
"""Read a json file and return a dict."""
|
"""Read a json file and return a dict."""
|
||||||
with jsonfile.open('r') as cfile:
|
with jsonfile.open('r') as cfile:
|
||||||
return json.loads(cfile.read())
|
return json.loads(cfile.read())
|
||||||
|
|
||||||
|
|
||||||
|
def validate_timezone(timezone):
|
||||||
|
"""Validate voluptuous timezone."""
|
||||||
|
try:
|
||||||
|
pytz.timezone(timezone)
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
raise vol.Invalid(
|
||||||
|
"Invalid time zone passed in. Valid options can be found here: "
|
||||||
|
"http://en.wikipedia.org/wiki/List_of_tz_database_time_zones") \
|
||||||
|
from None
|
||||||
|
|
||||||
|
return timezone
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_timezone(websession):
|
||||||
|
"""Read timezone from freegeoip."""
|
||||||
|
data = {}
|
||||||
|
with suppress(aiohttp.ClientError, asyncio.TimeoutError,
|
||||||
|
json.JSONDecodeError, KeyError):
|
||||||
|
with async_timeout.timeout(10, loop=websession.loop):
|
||||||
|
async with websession.get(FREEGEOIP_URL) as request:
|
||||||
|
data = await request.json()
|
||||||
|
|
||||||
|
return data.get('time_zone', 'UTC')
|
||||||
|
Submodule home-assistant-polymer updated: a341ccf944...c5a5f41d3c
3
setup.py
3
setup.py
@@ -39,6 +39,7 @@ setup(
|
|||||||
'voluptuous',
|
'voluptuous',
|
||||||
'gitpython',
|
'gitpython',
|
||||||
'pyotp',
|
'pyotp',
|
||||||
'pyqrcode'
|
'pyqrcode',
|
||||||
|
'pytz'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"hassio": "0.28",
|
"hassio": "0.37",
|
||||||
"homeassistant": "0.44.2",
|
"homeassistant": "0.46.1",
|
||||||
"resinos": "0.7",
|
"resinos": "0.8",
|
||||||
"resinhup": "0.1",
|
"resinhup": "0.1",
|
||||||
"generic": "0.3"
|
"generic": "0.3"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user