diff --git a/API.md b/API.md index 9211fc110ac..924e375a69e 100644 --- a/API.md +++ b/API.md @@ -34,6 +34,7 @@ The addons from `addons` are only installed one. "last_version": "LAST_VERSION", "arch": "armhf|aarch64|i386|amd64", "beta_channel": "true|false", + "timezone": "TIMEZONE", "addons": [ { "name": "xy bla", @@ -98,6 +99,7 @@ Optional: ```json { "beta_channel": "true|false", + "timezone": "TIMEZONE", "addons_repositories": [ "REPO_URL" ] @@ -176,6 +178,11 @@ Optional: ### Network - GET `/network/info` +```json +{ + "hostname": "" +} +``` - POST `/network/options` ```json diff --git a/README.md b/README.md index 481ae961691..45c0c12721a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ 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.** -## 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 diff --git a/hassio/addons/data.py b/hassio/addons/data.py index c9e13482301..a4da39d10be 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -16,7 +16,7 @@ FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO, ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY, ATTR_URL, ATTR_ARCH, - ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT) + ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT, ATTR_HOST_NETWORK) from ..config import Config from ..tools import read_json_file, write_json_file @@ -294,6 +294,12 @@ def get_ports(self, addon): """Return ports of addon.""" 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): """Return devices of addon.""" return self._system_data[addon].get(ATTR_DEVICES) diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index e3f4cd262e6..96842e30c47 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -6,7 +6,8 @@ ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER, STARTUP_BEFORE, STARTUP_INITIALIZE, BOOT_AUTO, BOOT_MANUAL, ATTR_SCHEMA, ATTR_IMAGE, ATTR_URL, ATTR_MAINTAINER, ATTR_ARCH, ATTR_DEVICES, - ATTR_ENVIRONMENT, ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386) + ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, + ARCH_I386) MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$" @@ -24,8 +25,19 @@ ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386 ] + +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 -SCHEMA_ADDON_CONFIG = vol.Schema({ +SCHEMA_ADDON_CONFIG = vol.Schema(vol.All({ vol.Required(ATTR_NAME): vol.Coerce(str), vol.Required(ATTR_VERSION): vol.Coerce(str), vol.Required(ATTR_SLUG): vol.Coerce(str), @@ -38,6 +50,7 @@ vol.Required(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]), 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_MAP, default=[]): [vol.Match(MAP_VOLUME)], vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)}, @@ -48,7 +61,7 @@ ]) }, 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 diff --git a/hassio/api/network.py b/hassio/api/network.py index 1d622edcabe..8c42b3a05d2 100644 --- a/hassio/api/network.py +++ b/hassio/api/network.py @@ -1,11 +1,19 @@ """Init file for HassIO network rest api.""" 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__) +SCHEMA_OPTIONS = vol.Schema({ + vol.Optional(ATTR_HOSTNAME): vol.Coerce(str), +}) + + class APINetwork(object): """Handle rest api for network functions.""" @@ -15,12 +23,21 @@ def __init__(self, config, loop, host_control): self.loop = loop self.host_control = host_control - @api_process_hostcontrol - def info(self, request): + @api_process + async def info(self, request): """Show network settings.""" - pass + return { + ATTR_HOSTNAME: self.host_control.hostname, + } @api_process_hostcontrol - def options(self, request): + async def options(self, request): """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 diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index e4e0ae32bad..0b51d9be4e8 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -11,7 +11,8 @@ HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES, ATTR_REPOSITORY, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, 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__) @@ -19,6 +20,7 @@ # pylint: disable=no-value-for-parameter vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(), vol.Optional(ATTR_ADDONS_REPOSITORIES): [vol.Url()], + vol.Optional(ATTR_TIMEZONE): validate_timezone, }) SCHEMA_VERSION = vol.Schema({ @@ -92,6 +94,7 @@ async def info(self, request): ATTR_LAST_VERSION: self.config.last_hassio, ATTR_BETA_CHANNEL: self.config.upstream_beta, ATTR_ARCH: self.addons.arch, + ATTR_TIMEZONE: self.config.timezone, ATTR_ADDONS: self._addons_list(only_installed=True), ATTR_ADDONS_REPOSITORIES: self.config.addons_repositories, } @@ -112,6 +115,9 @@ async def options(self, request): if ATTR_BETA_CHANNEL in body: 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: new = set(body[ATTR_ADDONS_REPOSITORIES]) old = set(self.config.addons_repositories) diff --git a/hassio/config.py b/hassio/config.py index fd295cbacd5..7c0ff16f2dc 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -10,7 +10,7 @@ from .const import FILE_HASSIO_CONFIG, HASSIO_SHARE 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__) @@ -35,8 +35,8 @@ SHARE_DATA = PurePath("share") UPSTREAM_BETA = 'upstream_beta' - API_ENDPOINT = 'api_endpoint' +TIMEZONE = 'timezone' SECURITY_INITIALIZE = 'security_initialize' SECURITY_TOTP = 'security_totp' @@ -48,6 +48,7 @@ SCHEMA_CONFIG = vol.Schema({ vol.Optional(UPSTREAM_BETA, default=False): vol.Boolean(), vol.Optional(API_ENDPOINT): vol.Coerce(str), + vol.Optional(TIMEZONE, default='UTC'): validate_timezone, vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str), vol.Optional(HASSIO_LAST): vol.Coerce(str), vol.Optional(HASSIO_CLEANUP): vol.Coerce(str), @@ -136,6 +137,17 @@ def upstream_beta(self, value): """Set beta upstream mode.""" self._data[UPSTREAM_BETA] = bool(value) + @property + def timezone(self): + """Return system timezone.""" + return self._data[TIMEZONE] + + @timezone.setter + def timezone(self, value): + """Set system timezone.""" + self._data[TIMEZONE] = value + self.save() + @property def hassio_cleanup(self): """Return Version they need to cleanup.""" diff --git a/hassio/const.py b/hassio/const.py index 53afed56724..86cc8b5c045 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,7 +1,7 @@ """Const file for HassIO.""" from pathlib import Path -HASSIO_VERSION = '0.31' +HASSIO_VERSION = '0.32' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' 'hassio/master/version.json') @@ -43,6 +43,7 @@ ATTR_ARCH = 'arch' ATTR_HOSTNAME = 'hostname' +ATTR_TIMEZONE = 'timezone' ATTR_OS = 'os' ATTR_TYPE = 'type' ATTR_SOURCE = 'source' @@ -77,6 +78,7 @@ ATTR_BUILD = 'build' ATTR_DEVICES = 'devices' ATTR_ENVIRONMENT = 'environment' +ATTR_HOST_NETWORK = 'host_network' STARTUP_INITIALIZE = 'initialize' STARTUP_BEFORE = 'before' diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index 1dd1948d314..6c8a0ba8b68 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -28,6 +28,16 @@ def docker_name(self): """Return name of docker container.""" 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 volumes(self): """Generate volumes for mappings.""" @@ -86,11 +96,11 @@ def _run(self): self.image, name=self.docker_name, detach=True, - network_mode='bridge', + network_mode=self.addons_data.get_network_mode(self.addon), ports=self.addons_data.get_ports(self.addon), devices=self.addons_data.get_devices(self.addon), - environment=self.addons_data.get_environment(self.addon), - volumes=self.volumes, + environment=self.environment, + volumes=self.volumes ) self.process_metadata() diff --git a/hassio/dock/homeassistant.py b/hassio/dock/homeassistant.py index 588a0b1dd42..f4c298f8b91 100644 --- a/hassio/dock/homeassistant.py +++ b/hassio/dock/homeassistant.py @@ -42,6 +42,7 @@ def _run(self): network_mode='host', environment={ 'HASSIO': self.config.api_endpoint, + 'TZ': self.config.timezone, }, volumes={ str(self.config.path_extern_config): diff --git a/hassio/host_control.py b/hassio/host_control.py index 845742f27ac..54290e40625 100644 --- a/hassio/host_control.py +++ b/hassio/host_control.py @@ -17,6 +17,7 @@ FEATURES_SHUTDOWN = 'shutdown' FEATURES_REBOOT = 'reboot' FEATURES_UPDATE = 'update' +FEATURES_HOSTNAME = 'hostname' FEATURES_NETWORK_INFO = 'network_info' FEATURES_NETWORK_CONTROL = 'network_control' @@ -117,3 +118,7 @@ def update(self, version=None): if version: return self._send_command("update {}".format(version)) return self._send_command("update") + + def set_hostname(self, hostname): + """Update hostname on host.""" + return self._send_command("hostname {}".format(hostname)) diff --git a/hassio/tools.py b/hassio/tools.py index 7fc3562bb10..6eedce39a72 100644 --- a/hassio/tools.py +++ b/hassio/tools.py @@ -7,6 +7,8 @@ import aiohttp import async_timeout +import pytz +import voluptuous as vol from .const import URL_HASSIO_VERSION, URL_HASSIO_VERSION_BETA @@ -90,3 +92,16 @@ def read_json_file(jsonfile): """Read a json file and return a dict.""" with jsonfile.open('r') as cfile: 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 diff --git a/setup.py b/setup.py index 14374be3a7a..8ffc0f50727 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ 'voluptuous', 'gitpython', 'pyotp', - 'pyqrcode' + 'pyqrcode', + 'pytz' ] ) diff --git a/version.json b/version.json index d4290ff330e..3615e0a1209 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "hassio": "0.31", + "hassio": "0.32", "homeassistant": "0.45", "resinos": "0.8", "resinhup": "0.1",