mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 22:56:31 +00:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
commit
cb15602814
5
API.md
5
API.md
@ -111,7 +111,8 @@ Output is the raw docker log.
|
|||||||
{
|
{
|
||||||
"slug": "SLUG",
|
"slug": "SLUG",
|
||||||
"date": "ISO",
|
"date": "ISO",
|
||||||
"name": "Custom name"
|
"name": "Custom name",
|
||||||
|
"type": "full|partial"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -417,7 +418,7 @@ Get all available addons.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For reset custom network/audio settings, set it `null`.
|
Reset custom network/audio/options, set it `null`.
|
||||||
|
|
||||||
- POST `/addons/{addon}/start`
|
- POST `/addons/{addon}/start`
|
||||||
|
|
||||||
|
@ -87,25 +87,25 @@ class Addon(CoreSysAttributes):
|
|||||||
ATTR_OPTIONS: {},
|
ATTR_OPTIONS: {},
|
||||||
ATTR_VERSION: version,
|
ATTR_VERSION: version,
|
||||||
}
|
}
|
||||||
self._data.save()
|
self._data.save_data()
|
||||||
|
|
||||||
def _set_uninstall(self):
|
def _set_uninstall(self):
|
||||||
"""Set addon as uninstalled."""
|
"""Set addon 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()
|
self._data.save_data()
|
||||||
|
|
||||||
def _set_update(self, version):
|
def _set_update(self, version):
|
||||||
"""Update version of addon."""
|
"""Update version of addon."""
|
||||||
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][ATTR_VERSION] = version
|
||||||
self._data.save()
|
self._data.save_data()
|
||||||
|
|
||||||
def _restore_data(self, user, system):
|
def _restore_data(self, user, system):
|
||||||
"""Restore data to addon."""
|
"""Restore data to addon."""
|
||||||
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()
|
self._data.save_data()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self):
|
def options(self):
|
||||||
@ -120,8 +120,10 @@ class Addon(CoreSysAttributes):
|
|||||||
@options.setter
|
@options.setter
|
||||||
def options(self, value):
|
def options(self, value):
|
||||||
"""Store user addon options."""
|
"""Store user addon options."""
|
||||||
self._data.user[self._id][ATTR_OPTIONS] = deepcopy(value)
|
if value is None:
|
||||||
self._data.save()
|
self._data.user[self._id][ATTR_OPTIONS] = {}
|
||||||
|
else:
|
||||||
|
self._data.user[self._id][ATTR_OPTIONS] = deepcopy(value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def boot(self):
|
def boot(self):
|
||||||
@ -134,7 +136,6 @@ class Addon(CoreSysAttributes):
|
|||||||
def boot(self, value):
|
def boot(self, value):
|
||||||
"""Store user boot options."""
|
"""Store user boot options."""
|
||||||
self._data.user[self._id][ATTR_BOOT] = value
|
self._data.user[self._id][ATTR_BOOT] = value
|
||||||
self._data.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_update(self):
|
def auto_update(self):
|
||||||
@ -147,7 +148,6 @@ class Addon(CoreSysAttributes):
|
|||||||
def auto_update(self, value):
|
def auto_update(self, value):
|
||||||
"""Set auto update."""
|
"""Set auto update."""
|
||||||
self._data.user[self._id][ATTR_AUTO_UPDATE] = value
|
self._data.user[self._id][ATTR_AUTO_UPDATE] = value
|
||||||
self._data.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -225,8 +225,6 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
self._data.user[self._id][ATTR_NETWORK] = new_ports
|
self._data.user[self._id][ATTR_NETWORK] = new_ports
|
||||||
|
|
||||||
self._data.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def webui(self):
|
def webui(self):
|
||||||
"""Return URL to webui or None."""
|
"""Return URL to webui or None."""
|
||||||
@ -347,7 +345,6 @@ class Addon(CoreSysAttributes):
|
|||||||
self._data.user[self._id].pop(ATTR_AUDIO_OUTPUT, None)
|
self._data.user[self._id].pop(ATTR_AUDIO_OUTPUT, None)
|
||||||
else:
|
else:
|
||||||
self._data.user[self._id][ATTR_AUDIO_OUTPUT] = value
|
self._data.user[self._id][ATTR_AUDIO_OUTPUT] = value
|
||||||
self._data.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_input(self):
|
def audio_input(self):
|
||||||
@ -367,7 +364,6 @@ class Addon(CoreSysAttributes):
|
|||||||
self._data.user[self._id].pop(ATTR_AUDIO_INPUT, None)
|
self._data.user[self._id].pop(ATTR_AUDIO_INPUT, None)
|
||||||
else:
|
else:
|
||||||
self._data.user[self._id][ATTR_AUDIO_INPUT] = value
|
self._data.user[self._id][ATTR_AUDIO_INPUT] = value
|
||||||
self._data.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
@ -448,6 +444,10 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return path to addon changelog."""
|
"""Return path to addon changelog."""
|
||||||
return Path(self.path_location, 'CHANGELOG.md')
|
return Path(self.path_location, 'CHANGELOG.md')
|
||||||
|
|
||||||
|
def save_data(self):
|
||||||
|
"""Save data of addon."""
|
||||||
|
self._addons.data.save_data()
|
||||||
|
|
||||||
def write_options(self):
|
def write_options(self):
|
||||||
"""Return True if addon options is written to data."""
|
"""Return True if addon options is written to data."""
|
||||||
schema = self.schema
|
schema = self.schema
|
||||||
@ -455,10 +455,14 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
schema(options)
|
schema(options)
|
||||||
return write_json_file(self.path_options, options)
|
write_json_file(self.path_options, options)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error("Addon %s have wrong options: %s", self._id,
|
_LOGGER.error("Addon %s have wrong options: %s", self._id,
|
||||||
humanize_error(options, ex))
|
humanize_error(options, ex))
|
||||||
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
|
_LOGGER.error("Addon %s can't write options: %s", self._id, err)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -654,8 +658,10 @@ class Addon(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# store local configs/state
|
# store local configs/state
|
||||||
if not write_json_file(Path(temp, "addon.json"), data):
|
try:
|
||||||
_LOGGER.error("Can't write addon.json for %s", self._id)
|
write_json_file(Path(temp, "addon.json"), data)
|
||||||
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
|
_LOGGER.error("Can't save meta for %s: %s", self._id, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# write into tarfile
|
# write into tarfile
|
||||||
|
@ -10,18 +10,23 @@ from ..utils.json import JsonConfig
|
|||||||
class AddonBuild(JsonConfig, CoreSysAttributes):
|
class AddonBuild(JsonConfig, CoreSysAttributes):
|
||||||
"""Handle build options for addons."""
|
"""Handle build options for addons."""
|
||||||
|
|
||||||
def __init__(self, coresys, addon):
|
def __init__(self, coresys, slug):
|
||||||
"""Initialize addon builder."""
|
"""Initialize addon builder."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.addon = addon
|
self._id = slug
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
Path(addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
Path(self.addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
||||||
|
|
||||||
def save(self):
|
def save_data(self):
|
||||||
"""Ignore save function."""
|
"""Ignore save function."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def addon(self):
|
||||||
|
"""Return addon of build data."""
|
||||||
|
return self._addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_image(self):
|
def base_image(self):
|
||||||
"""Base images for this addon."""
|
"""Base images for this addon."""
|
||||||
|
@ -159,4 +159,4 @@ class Data(JsonConfig, CoreSysAttributes):
|
|||||||
have_change = True
|
have_change = True
|
||||||
|
|
||||||
if have_change:
|
if have_change:
|
||||||
self.save()
|
self.save_data()
|
||||||
|
@ -101,7 +101,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_AUTO_UART, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UART, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_TMPFS):
|
vol.Optional(ATTR_TMPFS):
|
||||||
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
||||||
vol.Optional(ATTR_MAP, default=[]): [vol.Match(RE_VOLUME)],
|
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
|
||||||
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
||||||
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
||||||
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
||||||
@ -141,7 +141,7 @@ SCHEMA_BUILD_CONFIG = vol.Schema({
|
|||||||
vol.In(ARCH_ALL): vol.Match(r"(?:^[\w{}]+/)?[\-\w{}]+:[\.\-\w{}]+$"),
|
vol.In(ARCH_ALL): vol.Match(r"(?:^[\w{}]+/)?[\-\w{}]+:[\.\-\w{}]+$"),
|
||||||
}),
|
}),
|
||||||
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ARGS, default={}): vol.Schema({
|
vol.Optional(ATTR_ARGS, default=dict): vol.Schema({
|
||||||
vol.Coerce(str): vol.Coerce(str)
|
vol.Coerce(str): vol.Coerce(str)
|
||||||
}),
|
}),
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
@ -152,7 +152,7 @@ SCHEMA_ADDON_USER = vol.Schema({
|
|||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
vol.Match(r"^[0-9a-f]{32}$"),
|
||||||
vol.Optional(ATTR_OPTIONS, default={}): 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):
|
||||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||||
@ -169,10 +169,10 @@ SCHEMA_ADDON_SYSTEM = SCHEMA_ADDON_CONFIG.extend({
|
|||||||
|
|
||||||
|
|
||||||
SCHEMA_ADDON_FILE = vol.Schema({
|
SCHEMA_ADDON_FILE = vol.Schema({
|
||||||
vol.Optional(ATTR_USER, default={}): {
|
vol.Optional(ATTR_USER, default=dict): {
|
||||||
vol.Coerce(str): SCHEMA_ADDON_USER,
|
vol.Coerce(str): SCHEMA_ADDON_USER,
|
||||||
},
|
},
|
||||||
vol.Optional(ATTR_SYSTEM, default={}): {
|
vol.Optional(ATTR_SYSTEM, default=dict): {
|
||||||
vol.Coerce(str): SCHEMA_ADDON_SYSTEM,
|
vol.Coerce(str): SCHEMA_ADDON_SYSTEM,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -140,7 +140,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
addon_schema = SCHEMA_OPTIONS.extend({
|
addon_schema = SCHEMA_OPTIONS.extend({
|
||||||
vol.Optional(ATTR_OPTIONS): 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)
|
||||||
@ -158,6 +158,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
if ATTR_AUDIO_OUTPUT in body:
|
if ATTR_AUDIO_OUTPUT in body:
|
||||||
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||||
|
|
||||||
|
addon.save_data()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -11,7 +11,7 @@ from ..const import (
|
|||||||
ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX,
|
ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX,
|
||||||
ATTR_BLK_READ, ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
ATTR_BLK_READ, ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import NETWORK_PORT
|
from ..validate import NETWORK_PORT, DOCKER_IMAGE
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ SCHEMA_OPTIONS = vol.Schema({
|
|||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
||||||
vol.Any(None, vol.Coerce(str)),
|
vol.Any(None, vol.Coerce(str)),
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
||||||
vol.Any(None, vol.Coerce(str)),
|
vol.Any(None, DOCKER_IMAGE),
|
||||||
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_SSL): vol.Boolean(),
|
vol.Optional(ATTR_SSL): vol.Boolean(),
|
||||||
@ -75,7 +75,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
if ATTR_WATCHDOG in body:
|
if ATTR_WATCHDOG in body:
|
||||||
self._homeassistant.watchdog = body[ATTR_WATCHDOG]
|
self._homeassistant.watchdog = body[ATTR_WATCHDOG]
|
||||||
|
|
||||||
self._homeassistant.save()
|
self._homeassistant.save_data()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -49,6 +49,7 @@ class APIHost(CoreSysAttributes):
|
|||||||
if ATTR_AUDIO_INPUT in body:
|
if ATTR_AUDIO_INPUT in body:
|
||||||
self._config.audio_input = body[ATTR_AUDIO_INPUT]
|
self._config.audio_input = body[ATTR_AUDIO_INPUT]
|
||||||
|
|
||||||
|
self._config.save_data()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process_hostcontrol
|
@api_process_hostcontrol
|
||||||
|
@ -18,8 +18,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_RESTORE_PARTIAL = vol.Schema({
|
SCHEMA_RESTORE_PARTIAL = vol.Schema({
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ADDONS): [vol.Coerce(str)],
|
vol.Optional(ATTR_ADDONS):
|
||||||
vol.Optional(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
|
vol.All([vol.Coerce(str)], vol.Unique()),
|
||||||
|
vol.Optional(ATTR_FOLDERS):
|
||||||
|
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||||
})
|
})
|
||||||
|
|
||||||
SCHEMA_SNAPSHOT_FULL = vol.Schema({
|
SCHEMA_SNAPSHOT_FULL = vol.Schema({
|
||||||
@ -27,8 +29,10 @@ SCHEMA_SNAPSHOT_FULL = vol.Schema({
|
|||||||
})
|
})
|
||||||
|
|
||||||
SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({
|
SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({
|
||||||
vol.Optional(ATTR_ADDONS): [vol.Coerce(str)],
|
vol.Optional(ATTR_ADDONS):
|
||||||
vol.Optional(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
|
vol.All([vol.Coerce(str)], vol.Unique()),
|
||||||
|
vol.Optional(ATTR_FOLDERS):
|
||||||
|
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -51,6 +55,7 @@ class APISnapshots(CoreSysAttributes):
|
|||||||
ATTR_SLUG: snapshot.slug,
|
ATTR_SLUG: snapshot.slug,
|
||||||
ATTR_NAME: snapshot.name,
|
ATTR_NAME: snapshot.name,
|
||||||
ATTR_DATE: snapshot.date,
|
ATTR_DATE: snapshot.date,
|
||||||
|
ATTR_TYPE: snapshot.sys_type,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -13,14 +13,14 @@ from ..const import (
|
|||||||
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
||||||
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import validate_timezone, WAIT_BOOT
|
from ..validate import validate_timezone, WAIT_BOOT, REPOSITORIES
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_OPTIONS = vol.Schema({
|
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): REPOSITORIES,
|
||||||
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
||||||
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
|
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
|
||||||
})
|
})
|
||||||
@ -84,8 +84,8 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
new = set(body[ATTR_ADDONS_REPOSITORIES])
|
new = set(body[ATTR_ADDONS_REPOSITORIES])
|
||||||
await asyncio.shield(self._addons.load_repositories(new))
|
await asyncio.shield(self._addons.load_repositories(new))
|
||||||
|
|
||||||
self._updater.save()
|
self._updater.save_data()
|
||||||
self._config.save()
|
self._config.save_data()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -45,7 +45,6 @@ class CoreConfig(JsonConfig):
|
|||||||
def timezone(self, value):
|
def timezone(self, value):
|
||||||
"""Set system timezone."""
|
"""Set system timezone."""
|
||||||
self._data[ATTR_TIMEZONE] = value
|
self._data[ATTR_TIMEZONE] = value
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wait_boot(self):
|
def wait_boot(self):
|
||||||
@ -56,7 +55,6 @@ class CoreConfig(JsonConfig):
|
|||||||
def wait_boot(self, value):
|
def wait_boot(self, value):
|
||||||
"""Set wait boot time."""
|
"""Set wait boot time."""
|
||||||
self._data[ATTR_WAIT_BOOT] = value
|
self._data[ATTR_WAIT_BOOT] = value
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_boot(self):
|
def last_boot(self):
|
||||||
@ -72,7 +70,6 @@ class CoreConfig(JsonConfig):
|
|||||||
def last_boot(self, value):
|
def last_boot(self, value):
|
||||||
"""Set last boot datetime."""
|
"""Set last boot datetime."""
|
||||||
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_hassio(self):
|
def path_hassio(self):
|
||||||
@ -170,7 +167,6 @@ class CoreConfig(JsonConfig):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._data[ATTR_ADDONS_CUSTOM_LIST].append(repo)
|
self._data[ATTR_ADDONS_CUSTOM_LIST].append(repo)
|
||||||
self.save()
|
|
||||||
|
|
||||||
def drop_addon_repository(self, repo):
|
def drop_addon_repository(self, repo):
|
||||||
"""Remove a custom repository from list."""
|
"""Remove a custom repository from list."""
|
||||||
@ -178,7 +174,6 @@ class CoreConfig(JsonConfig):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo)
|
self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo)
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_output(self):
|
def audio_output(self):
|
||||||
@ -189,7 +184,6 @@ class CoreConfig(JsonConfig):
|
|||||||
def audio_output(self, value):
|
def audio_output(self, value):
|
||||||
"""Set ALSA audio output card,dev."""
|
"""Set ALSA audio output card,dev."""
|
||||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_input(self):
|
def audio_input(self):
|
||||||
@ -200,4 +194,3 @@ class CoreConfig(JsonConfig):
|
|||||||
def audio_input(self, value):
|
def audio_input(self, value):
|
||||||
"""Set ALSA audio input card,dev."""
|
"""Set ALSA audio input card,dev."""
|
||||||
self._data[ATTR_AUDIO_INPUT] = value
|
self._data[ATTR_AUDIO_INPUT] = value
|
||||||
self.save()
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '0.82'
|
HASSIO_VERSION = '0.83'
|
||||||
|
|
||||||
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
||||||
'hassio/{}/version.json')
|
'hassio/{}/version.json')
|
||||||
|
@ -26,7 +26,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def addon(self):
|
def addon(self):
|
||||||
"""Return name of docker image."""
|
"""Return addon of docker image."""
|
||||||
return self._addons.get(self._id)
|
return self._addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -269,7 +269,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
build_env = AddonBuild(self.coresys, self.addon)
|
build_env = AddonBuild(self.coresys, self._id)
|
||||||
|
|
||||||
_LOGGER.info("Start build %s:%s", self.image, tag)
|
_LOGGER.info("Start build %s:%s", self.image, tag)
|
||||||
try:
|
try:
|
||||||
|
@ -53,7 +53,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
def api_port(self, value):
|
def api_port(self, value):
|
||||||
"""Set network port for home-assistant instance."""
|
"""Set network port for home-assistant instance."""
|
||||||
self._data[ATTR_PORT] = value
|
self._data[ATTR_PORT] = value
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_password(self):
|
def api_password(self):
|
||||||
|
@ -112,14 +112,15 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
_LOGGER.info("Full-Snapshot %s store folders", snapshot.slug)
|
_LOGGER.info("Full-Snapshot %s store folders", snapshot.slug)
|
||||||
await snapshot.store_folders()
|
await snapshot.store_folders()
|
||||||
|
|
||||||
_LOGGER.info("Full-Snapshot %s done", snapshot.slug)
|
|
||||||
self.snapshots_obj[snapshot.slug] = snapshot
|
|
||||||
return True
|
|
||||||
|
|
||||||
except (OSError, ValueError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Full-Snapshot %s error: %s", snapshot.slug, err)
|
_LOGGER.info("Full-Snapshot %s error: %s", snapshot.slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Full-Snapshot %s done", snapshot.slug)
|
||||||
|
self.snapshots_obj[snapshot.slug] = snapshot
|
||||||
|
return True
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._scheduler.suspend = False
|
self._scheduler.suspend = False
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
@ -157,14 +158,15 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
snapshot.slug, folders)
|
snapshot.slug, folders)
|
||||||
await snapshot.store_folders(folders)
|
await snapshot.store_folders(folders)
|
||||||
|
|
||||||
_LOGGER.info("Partial-Snapshot %s done", snapshot.slug)
|
|
||||||
self.snapshots_obj[snapshot.slug] = snapshot
|
|
||||||
return True
|
|
||||||
|
|
||||||
except (OSError, ValueError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Partial-Snapshot %s error: %s", snapshot.slug, err)
|
_LOGGER.info("Partial-Snapshot %s error: %s", snapshot.slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Partial-Snapshot %s done", snapshot.slug)
|
||||||
|
self.snapshots_obj[snapshot.slug] = snapshot
|
||||||
|
return True
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._scheduler.suspend = False
|
self._scheduler.suspend = False
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
@ -229,7 +231,7 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
if addon:
|
if addon:
|
||||||
tasks.append(addon.uninstall())
|
tasks.append(addon.uninstall())
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Can't remove addon %s", slug)
|
_LOGGER.warning("Can't remove addon %s", snapshot.slug)
|
||||||
|
|
||||||
for slug in restore_addons:
|
for slug in restore_addons:
|
||||||
addon = self._addons.get(slug)
|
addon = self._addons.get(slug)
|
||||||
@ -249,13 +251,14 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
await task_hass
|
await task_hass
|
||||||
await self._homeassistant.run()
|
await self._homeassistant.run()
|
||||||
|
|
||||||
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
|
_LOGGER.info("Full-Restore %s error: %s", snapshot.slug, err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
_LOGGER.info("Full-Restore %s done", snapshot.slug)
|
_LOGGER.info("Full-Restore %s done", snapshot.slug)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, ValueError, tarfile.TarError) as err:
|
|
||||||
_LOGGER.info("Full-Restore %s error: %s", slug, err)
|
|
||||||
return False
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._scheduler.suspend = False
|
self._scheduler.suspend = False
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
@ -298,7 +301,8 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
if addon:
|
if addon:
|
||||||
tasks.append(snapshot.export_addon(addon))
|
tasks.append(snapshot.export_addon(addon))
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Can't restore addon %s", slug)
|
_LOGGER.warning("Can't restore addon %s",
|
||||||
|
snapshot.slug)
|
||||||
|
|
||||||
if tasks:
|
if tasks:
|
||||||
_LOGGER.info("Partial-Restore %s run %d tasks",
|
_LOGGER.info("Partial-Restore %s run %d tasks",
|
||||||
@ -308,13 +312,14 @@ class SnapshotsManager(CoreSysAttributes):
|
|||||||
# make sure homeassistant run agen
|
# make sure homeassistant run agen
|
||||||
await self._homeassistant.run()
|
await self._homeassistant.run()
|
||||||
|
|
||||||
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
|
_LOGGER.info("Partial-Restore %s error: %s", snapshot.slug, err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
|
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, ValueError, tarfile.TarError) as err:
|
|
||||||
_LOGGER.info("Partial-Restore %s error: %s", slug, err)
|
|
||||||
return False
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._scheduler.suspend = False
|
self._scheduler.suspend = False
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
@ -244,12 +244,13 @@ class Snapshot(CoreSysAttributes):
|
|||||||
with tarfile.open(self.tar_file, "w:") as tar:
|
with tarfile.open(self.tar_file, "w:") as tar:
|
||||||
tar.add(self._tmp.name, arcname=".")
|
tar.add(self._tmp.name, arcname=".")
|
||||||
|
|
||||||
if write_json_file(Path(self._tmp.name, "snapshot.json"), self._data):
|
try:
|
||||||
|
write_json_file(Path(self._tmp.name, "snapshot.json"), self._data)
|
||||||
await self._loop.run_in_executor(None, _create_snapshot)
|
await self._loop.run_in_executor(None, _create_snapshot)
|
||||||
else:
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
_LOGGER.error("Can't write snapshot.json")
|
_LOGGER.error("Can't write snapshot: %s", err)
|
||||||
|
finally:
|
||||||
self._tmp.cleanup()
|
self._tmp.cleanup()
|
||||||
|
|
||||||
async def import_addon(self, addon):
|
async def import_addon(self, addon):
|
||||||
"""Add a addon into snapshot."""
|
"""Add a addon into snapshot."""
|
||||||
@ -280,7 +281,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
async def store_folders(self, folder_list=None):
|
async def store_folders(self, folder_list=None):
|
||||||
"""Backup hassio data into snapshot."""
|
"""Backup hassio data into snapshot."""
|
||||||
folder_list = folder_list or ALL_FOLDERS
|
folder_list = set(folder_list or ALL_FOLDERS)
|
||||||
|
|
||||||
def _folder_save(name):
|
def _folder_save(name):
|
||||||
"""Intenal function to snapshot a folder."""
|
"""Intenal function to snapshot a folder."""
|
||||||
@ -293,8 +294,8 @@ class Snapshot(CoreSysAttributes):
|
|||||||
with tarfile.open(snapshot_tar, "w:gz",
|
with tarfile.open(snapshot_tar, "w:gz",
|
||||||
compresslevel=1) as tar_file:
|
compresslevel=1) as tar_file:
|
||||||
tar_file.add(origin_dir, arcname=".")
|
tar_file.add(origin_dir, arcname=".")
|
||||||
_LOGGER.info("Snapshot folder %s done", name)
|
|
||||||
|
|
||||||
|
_LOGGER.info("Snapshot folder %s done", name)
|
||||||
self._data[ATTR_FOLDERS].append(name)
|
self._data[ATTR_FOLDERS].append(name)
|
||||||
except (tarfile.TarError, OSError) as err:
|
except (tarfile.TarError, OSError) as err:
|
||||||
_LOGGER.warning("Can't snapshot folder %s: %s", name, err)
|
_LOGGER.warning("Can't snapshot folder %s: %s", name, err)
|
||||||
@ -307,7 +308,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
async def restore_folders(self, folder_list=None):
|
async def restore_folders(self, folder_list=None):
|
||||||
"""Backup hassio data into snapshot."""
|
"""Backup hassio data into snapshot."""
|
||||||
folder_list = folder_list or ALL_FOLDERS
|
folder_list = set(folder_list or self.folders)
|
||||||
|
|
||||||
def _folder_restore(name):
|
def _folder_restore(name):
|
||||||
"""Intenal function to restore a folder."""
|
"""Intenal function to restore a folder."""
|
||||||
@ -323,7 +324,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
_LOGGER.info("Restore folder %s", name)
|
_LOGGER.info("Restore folder %s", name)
|
||||||
with tarfile.open(snapshot_tar, "r:gz") as tar_file:
|
with tarfile.open(snapshot_tar, "r:gz") as tar_file:
|
||||||
tar_file.extractall(path=origin_dir)
|
tar_file.extractall(path=origin_dir)
|
||||||
_LOGGER.info("Restore folder %s done", name)
|
_LOGGER.info("Restore folder %s done", name)
|
||||||
except (tarfile.TarError, OSError) as err:
|
except (tarfile.TarError, OSError) as err:
|
||||||
_LOGGER.warning("Can't restore folder %s: %s", name, err)
|
_LOGGER.warning("Can't restore folder %s: %s", name, err)
|
||||||
|
|
||||||
@ -365,7 +366,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
self._homeassistant.api_password = self.homeassistant_password
|
self._homeassistant.api_password = self.homeassistant_password
|
||||||
|
|
||||||
# save
|
# save
|
||||||
self._homeassistant.save()
|
self._homeassistant.save_data()
|
||||||
|
|
||||||
def store_repositories(self):
|
def store_repositories(self):
|
||||||
"""Store repository list into snapshot."""
|
"""Store repository list into snapshot."""
|
||||||
|
@ -9,31 +9,42 @@ from ..const import (
|
|||||||
ATTR_LAST_VERSION,
|
ATTR_LAST_VERSION,
|
||||||
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
||||||
SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
|
SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
|
||||||
from ..validate import NETWORK_PORT
|
from ..validate import NETWORK_PORT, REPOSITORIES, DOCKER_IMAGE
|
||||||
|
|
||||||
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
||||||
|
|
||||||
|
|
||||||
|
def unique_addons(addons_list):
|
||||||
|
"""Validate that a add-on is unique."""
|
||||||
|
single = set([addon[ATTR_SLUG] for addon in addons_list])
|
||||||
|
|
||||||
|
if len(single) != len(addons_list):
|
||||||
|
raise vol.Invalid("Invalid addon list on snapshot!")
|
||||||
|
return addons_list
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_SNAPSHOT = vol.Schema({
|
SCHEMA_SNAPSHOT = vol.Schema({
|
||||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||||
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
|
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
|
||||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||||
vol.Required(ATTR_DATE): vol.Coerce(str),
|
vol.Required(ATTR_DATE): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT, default={}): vol.Schema({
|
vol.Optional(ATTR_HOMEASSISTANT, default=dict): vol.Schema({
|
||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_IMAGE): vol.Coerce(str),
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||||
vol.Optional(ATTR_LAST_VERSION): vol.Coerce(str),
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
||||||
}, extra=vol.REMOVE_EXTRA),
|
}, extra=vol.REMOVE_EXTRA),
|
||||||
vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)],
|
vol.Optional(ATTR_FOLDERS, default=list):
|
||||||
vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({
|
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||||
|
vol.Optional(ATTR_ADDONS, default=list): vol.All([vol.Schema({
|
||||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||||
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),
|
||||||
}, extra=vol.REMOVE_EXTRA)],
|
}, extra=vol.REMOVE_EXTRA)], unique_addons),
|
||||||
vol.Optional(ATTR_REPOSITORIES, default=[]): [vol.Url()],
|
vol.Optional(ATTR_REPOSITORIES, default=list): REPOSITORIES,
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
@ -89,4 +89,4 @@ class Updater(JsonConfig, CoreSysAttributes):
|
|||||||
# update versions
|
# update versions
|
||||||
self._data[ATTR_HOMEASSISTANT] = data.get('homeassistant')
|
self._data[ATTR_HOMEASSISTANT] = data.get('homeassistant')
|
||||||
self._data[ATTR_HASSIO] = data.get('hassio')
|
self._data[ATTR_HASSIO] = data.get('hassio')
|
||||||
self.save()
|
self.save_data()
|
||||||
|
@ -10,14 +10,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def write_json_file(jsonfile, data):
|
def write_json_file(jsonfile, data):
|
||||||
"""Write a json file."""
|
"""Write a json file."""
|
||||||
try:
|
json_str = json.dumps(data, indent=2)
|
||||||
json_str = json.dumps(data, indent=2)
|
with jsonfile.open('w') as conf_file:
|
||||||
with jsonfile.open('w') as conf_file:
|
conf_file.write(json_str)
|
||||||
conf_file.write(json_str)
|
|
||||||
except (OSError, json.JSONDecodeError):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def read_json_file(jsonfile):
|
def read_json_file(jsonfile):
|
||||||
@ -35,7 +30,10 @@ class JsonConfig(object):
|
|||||||
self._schema = schema
|
self._schema = schema
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
# init or load data
|
self.read_data()
|
||||||
|
|
||||||
|
def read_data(self):
|
||||||
|
"""Read json file & validate."""
|
||||||
if self._file.is_file():
|
if self._file.is_file():
|
||||||
try:
|
try:
|
||||||
self._data = read_json_file(self._file)
|
self._data = read_json_file(self._file)
|
||||||
@ -43,27 +41,33 @@ class JsonConfig(object):
|
|||||||
_LOGGER.warning("Can't read %s", self._file)
|
_LOGGER.warning("Can't read %s", self._file)
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
# validate
|
# Validate
|
||||||
try:
|
try:
|
||||||
self._data = self._schema(self._data)
|
self._data = self._schema(self._data)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error("Can't parse %s: %s",
|
_LOGGER.error("Can't parse %s: %s",
|
||||||
self._file, humanize_error(self._data, ex))
|
self._file, humanize_error(self._data, ex))
|
||||||
# reset data to default
|
|
||||||
|
# Reset data to default
|
||||||
|
_LOGGER.warning("Reset %s to default", self._file)
|
||||||
self._data = self._schema({})
|
self._data = self._schema({})
|
||||||
|
|
||||||
def save(self):
|
def save_data(self):
|
||||||
"""Store data to config file."""
|
"""Store data to config file."""
|
||||||
# validate
|
# Validate
|
||||||
try:
|
try:
|
||||||
self._data = self._schema(self._data)
|
self._data = self._schema(self._data)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error("Can't parse data: %s",
|
_LOGGER.error("Can't parse data: %s",
|
||||||
humanize_error(self._data, ex))
|
humanize_error(self._data, ex))
|
||||||
return False
|
|
||||||
|
# Load last valid data
|
||||||
|
_LOGGER.warning("Reset %s to last version", self._file)
|
||||||
|
self.save_data()
|
||||||
|
return
|
||||||
|
|
||||||
# write
|
# write
|
||||||
if not write_json_file(self._file, self._data):
|
try:
|
||||||
_LOGGER.error("Can't store config in %s", self._file)
|
write_json_file(self._file, self._data)
|
||||||
return False
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
return True
|
_LOGGER.error("Can't store config in %s: %s", self._file, err)
|
||||||
|
@ -14,6 +14,10 @@ from .const import (
|
|||||||
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||||
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
|
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
|
||||||
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
||||||
|
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
REPOSITORIES = vol.All([vol.Url()], vol.Unique())
|
||||||
|
|
||||||
|
|
||||||
def validate_timezone(timezone):
|
def validate_timezone(timezone):
|
||||||
@ -62,7 +66,7 @@ SCHEMA_HASS_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
vol.Match(r"^[0-9a-f]{32}$"),
|
||||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||||
@ -85,7 +89,7 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str),
|
vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[
|
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[
|
||||||
"https://github.com/hassio-addons/repository",
|
"https://github.com/hassio-addons/repository",
|
||||||
]): [vol.Url()],
|
]): REPOSITORIES,
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL,
|
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL,
|
vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL,
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hassio": "0.82",
|
"hassio": "0.83",
|
||||||
"homeassistant": "0.61.1",
|
"homeassistant": "0.61.1",
|
||||||
"resinos": "1.1",
|
"resinos": "1.1",
|
||||||
"resinhup": "0.3",
|
"resinhup": "0.3",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user