Skip to content

Commit

Permalink
feat: add support for new backup logic in HA Core 2025.1.0
Browse files Browse the repository at this point in the history
* fixes backups not working for HA Core users on 2025.1.0
* adds native support for backup name
* adds support for backup password

Fixes: #178
  • Loading branch information
jcwillox committed Jan 6, 2025
1 parent be30743 commit eb2e6da
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 73 deletions.
13 changes: 1 addition & 12 deletions custom_components/auto_backup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
CONF_AUTO_PURGE,
CONF_BACKUP_TIMEOUT,
DEFAULT_BACKUP_TIMEOUT,
PATCH_NAME,
)
from .handlers import SupervisorHandler, HassioAPIError, BackupHandler, HandlerBase

Expand Down Expand Up @@ -156,7 +155,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if is_hassio(hass):
handler = SupervisorHandler(getenv("SUPERVISOR"), async_get_clientsession(hass))
else:
handler = BackupHandler(hass.data[DOMAIN_BACKUP])
handler = BackupHandler(hass, hass.data[DOMAIN_BACKUP])

auto_backup = AutoBackup(hass, options, handler)
hass.data.setdefault(DOMAIN, {})[DATA_AUTO_BACKUP] = auto_backup
Expand Down Expand Up @@ -296,13 +295,6 @@ def generate_backup_name(self) -> str:
def validate_backup_config(self, config: Dict):
"""Validate the backup config."""
if not self._supervised:
disallowed_options = [ATTR_PASSWORD]
for option in disallowed_options:
if config.get(option):
raise HomeAssistantError(
f"The '{option}' option is not supported on non-supervised installations."
)

# allow `include` if it only contains the configuration
if ATTR_INCLUDE in config and not config.get(ATTR_EXCLUDE):
# ensure no addons were included
Expand All @@ -317,9 +309,6 @@ def validate_backup_config(self, config: Dict):
"Partial backups (e.g. include/exclude) are not supported on non-supervised installations."
)

if config.get(ATTR_NAME):
config[PATCH_NAME] = True

if not config.get(ATTR_NAME):
config[ATTR_NAME] = self.generate_backup_name()

Expand Down
2 changes: 0 additions & 2 deletions custom_components/auto_backup/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,4 @@
EVENT_BACKUP_FAILED = f"{DOMAIN}.backup_failed"
EVENT_BACKUPS_PURGED = f"{DOMAIN}.purged_backups"

PATCH_NAME = "patch.name"

PLATFORMS = [Platform.SENSOR]
90 changes: 32 additions & 58 deletions custom_components/auto_backup/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import aiohttp
from aiohttp.hdrs import AUTHORIZATION
from homeassistant.components.backup import BackupManager
from homeassistant.components.hassio import ATTR_PASSWORD
from homeassistant.const import ATTR_NAME
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import HomeAssistant

from .const import DEFAULT_BACKUP_TIMEOUT_SECONDS, PATCH_NAME
from .const import DEFAULT_BACKUP_TIMEOUT_SECONDS

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -168,8 +169,10 @@ async def download_backup(


class BackupHandler(HandlerBase):
def __init__(self, manager: BackupManager):
def __init__(self, hass: HomeAssistant, manager: BackupManager):
self._hass = hass
self._manager = manager
self._agent = manager

async def get_addons(self):
raise NotImplementedError("This should be unreachable")
Expand All @@ -178,69 +181,40 @@ async def get_addons(self):
async def create_backup(
self, config: Dict, partial: bool = False, timeout: Optional[int] = None
) -> Dict:
if not config.get(PATCH_NAME):
if hasattr(self._manager, "async_create_backup"):
backup = await self._manager.async_create_backup()
else:
backup = await self._manager.generate_backup()
else:
_LOGGER.debug("Support name is set: %s", config)
if not hasattr(self._manager, "_mkdir_and_generate_backup_contents"):
if not hasattr(self._manager, "_generate_backup_contents"):
raise HomeAssistantError(
"Unable to patch '_generate_backup_contents' function."
)
raise HomeAssistantError(
"Unable to patch '_mkdir_and_generate_backup_contents' function."
)
agent_id = list(self._manager.local_backup_agents)[0]
backup = await self._manager.async_create_backup(
agent_ids=[agent_id],
name=config.get(ATTR_NAME),
include_database=True,
include_folders=None,
include_homeassistant=True,
password=config.get(ATTR_PASSWORD),
# don't exist on HA Core
include_all_addons=False,
include_addons=None,
)
[backup, agent_errors] = await self._manager.async_get_backup(
backup.backup_job_id
)

def wrapper(*args, **kwargs):
if len(args) != 2 or kwargs or not isinstance(args[1], dict):
raise HomeAssistantError(
"Wrapper of '_mkdir_and_generate_backup_contents' called with wrong arguments"
)

args[1]["name"] = config[ATTR_NAME]
return old_function(*args, **kwargs)

if hasattr(self._manager, "_generate_backup_contents"):
old_function = self._manager._generate_backup_contents
else:
old_function = self._manager._mkdir_and_generate_backup_contents

try:
if hasattr(self._manager, "_generate_backup_contents"):
self._manager._generate_backup_contents = wrapper
else:
self._manager._mkdir_and_generate_backup_contents = wrapper
if hasattr(self._manager, "async_create_backup"):
backup = await self._manager.async_create_backup()
else:
backup = await self._manager.generate_backup()
backup.name = config[ATTR_NAME]
finally:
if hasattr(self._manager, "_generate_backup_contents"):
self._manager._generate_backup_contents = old_function
else:
self._manager._mkdir_and_generate_backup_contents = old_function

return backup.as_dict()
return {"slug": backup.backup_id, **backup.as_dict()}

async def remove_backup(self, slug):
if hasattr(self._manager, "async_remove_backup"):
await self._manager.async_remove_backup(slug=slug)
else:
await self._manager.remove_backup(slug)
await self._manager.async_delete_backup(slug)

async def download_backup(
self, slug: str, destination: str, timeout: int = DEFAULT_BACKUP_TIMEOUT_SECONDS
):
if hasattr(self._manager, "async_get_backup"):
backup = await self._manager.async_get_backup(slug=slug)
else:
backup = await self._manager.get_backup(slug)
[backup, agent_errors] = await self._manager.async_get_backup(slug)
if backup:
shutil.copyfile(backup.path, destination)
agent_id = list(self._manager.local_backup_agents)[0]
agent = self._manager.local_backup_agents[agent_id]
backup_path = agent.get_backup_path(backup.backup_id)

def _copyfile():
shutil.copyfile(backup_path, destination)

await self._hass.async_add_executor_job(_copyfile)
else:
_LOGGER.error(
"Cannot move backup (%s) to '%s' as it does not exist.",
Expand Down
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"zip_release": true,
"hide_default_branch": true,
"filename": "auto_backup.zip",
"homeassistant": "2024.11.0"
"homeassistant": "2025.1.0"
}

0 comments on commit eb2e6da

Please sign in to comment.