diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index ddfa43377..f033c7b60 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -65,6 +65,7 @@ from ..utils import check_port from ..utils.apparmor import adjust_profile from ..utils.json import read_json_file, write_json_file from ..utils.tar import atomic_contents_add, secure_path +from .const import SnapshotAddonMode from .model import AddonModel, Data from .options import AddonOptions from .utils import remove_data @@ -695,6 +696,8 @@ class Addon(AddonModel): async def snapshot(self, tar_file: tarfile.TarFile) -> None: """Snapshot state of an add-on.""" + is_running = await self.is_running() + with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp: temp_path = Path(temp) @@ -744,8 +747,15 @@ class Addon(AddonModel): arcname="data", ) - if self.snapshot_pre is not None: + if ( + is_running + and self.snapshot_mode == SnapshotAddonMode.HOT + and self.snapshot_pre is not None + ): await self._snapshot_command(self.snapshot_pre) + elif is_running and self.snapshot_mode == SnapshotAddonMode.COLD: + _LOGGER.info("Shutdown add-on %s for cold snapshot", self.slug) + await self.instance.stop() try: _LOGGER.info("Building snapshot for add-on %s", self.slug) @@ -754,8 +764,15 @@ class Addon(AddonModel): _LOGGER.error("Can't write tarfile %s: %s", tar_file, err) raise AddonsError() from err finally: - if self.snapshot_post is not None: + if ( + is_running + and self.snapshot_mode == SnapshotAddonMode.HOT + and self.snapshot_post is not None + ): await self._snapshot_command(self.snapshot_post) + elif is_running and self.snapshot_mode is SnapshotAddonMode.COLD: + _LOGGER.info("Starting add-on %s again", self.slug) + await self.start() _LOGGER.info("Finish snapshot for addon %s", self.slug) diff --git a/supervisor/addons/const.py b/supervisor/addons/const.py new file mode 100644 index 000000000..b82b226d1 --- /dev/null +++ b/supervisor/addons/const.py @@ -0,0 +1,12 @@ +"""Add-on static data.""" +from enum import Enum + + +class SnapshotAddonMode(str, Enum): + """Snapshot mode of an Add-on.""" + + HOT = "hot" + COLD = "cold" + + +ATTR_SNAPSHOT = "snapshot" diff --git a/supervisor/addons/model.py b/supervisor/addons/model.py index 5dcddd6b7..f8aca4446 100644 --- a/supervisor/addons/model.py +++ b/supervisor/addons/model.py @@ -5,6 +5,8 @@ from typing import Any, Awaitable, Dict, List, Optional from awesomeversion import AwesomeVersion, AwesomeVersionException +from supervisor.addons.const import SnapshotAddonMode + from ..const import ( ATTR_ADVANCED, ATTR_APPARMOR, @@ -76,6 +78,7 @@ from ..const import ( ) from ..coresys import CoreSys, CoreSysAttributes from ..docker.const import Capabilities +from .const import ATTR_SNAPSHOT from .options import AddonOptions, UiOptions from .validate import RE_SERVICE, RE_VOLUME @@ -370,6 +373,11 @@ class AddonModel(CoreSysAttributes, ABC): """Return post-snapshot command.""" return self.data.get(ATTR_SNAPSHOT_POST) + @property + def snapshot_mode(self) -> SnapshotAddonMode: + """Return if snapshot is hot/cold.""" + return self.data[ATTR_SNAPSHOT] + @property def default_init(self) -> bool: """Return True if the add-on have no own init.""" diff --git a/supervisor/addons/validate.py b/supervisor/addons/validate.py index 8b3482d81..f9104dd6e 100644 --- a/supervisor/addons/validate.py +++ b/supervisor/addons/validate.py @@ -7,6 +7,8 @@ import uuid import voluptuous as vol +from supervisor.addons.const import SnapshotAddonMode + from ..const import ( ARCH_ALL, ATTR_ACCESS_TOKEN, @@ -107,6 +109,7 @@ from ..validate import ( uuid_match, version_tag, ) +from .const import ATTR_SNAPSHOT from .options import RE_SCHEMA_ELEMENT _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -161,6 +164,14 @@ def _warn_addon_config(config: Dict[str, Any]): name, ) + if config.get(ATTR_SNAPSHOT, SnapshotAddonMode.HOT) == SnapshotAddonMode.COLD and ( + config.get(ATTR_SNAPSHOT_POST) or config.get(ATTR_SNAPSHOT_PRE) + ): + _LOGGER.warning( + "Add-on which only support COLD backups trying to use post/pre commands. Please report this to the maintainer of %s", + name, + ) + return config @@ -284,6 +295,9 @@ _SCHEMA_ADDON_CONFIG = vol.Schema( vol.Optional(ATTR_SNAPSHOT_EXCLUDE): [str], vol.Optional(ATTR_SNAPSHOT_PRE): str, vol.Optional(ATTR_SNAPSHOT_POST): str, + vol.Optional(ATTR_SNAPSHOT, default=SnapshotAddonMode.HOT): vol.Coerce( + SnapshotAddonMode + ), vol.Optional(ATTR_OPTIONS, default={}): dict, vol.Optional(ATTR_SCHEMA, default={}): vol.Any( vol.Schema(