HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /volume1/docker/homeassistant2025/custom_components/auto_backup/__init__.py
"""Component to create and automatically remove Home Assistant backups."""

import logging
from os import getenv

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.backup.const import DATA_MANAGER
from homeassistant.components.hassio import (
    ATTR_FOLDERS,
    ATTR_ADDONS,
    ATTR_PASSWORD,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.hassio import is_hassio

from .const import (
    ATTR_KEEP_DAYS,
    ATTR_DOWNLOAD_PATH,
    ATTR_COMPRESSED,
    ATTR_LOCATION,
    ATTR_EXCLUDE,
    ATTR_INCLUDE,
    ATTR_INCLUDE_ADDONS,
    ATTR_INCLUDE_FOLDERS,
    ATTR_EXCLUDE_ADDONS,
    ATTR_EXCLUDE_FOLDERS,
    SERVICE_BACKUP,
    SERVICE_BACKUP_FULL,
    SERVICE_BACKUP_PARTIAL,
    SERVICE_PURGE,
    CONF_AUTO_PURGE,
    CONF_BACKUP_TIMEOUT,
    DEFAULT_BACKUP_TIMEOUT,
    DATA_AUTO_BACKUP,
    DOMAIN,
    ATTR_ENCRYPTED,
    ATTR_EXCLUDE_DATABASE,
)
from .handlers import SupervisorHandler, BackupHandler
from .helpers import is_backup
from .manager import AutoBackup

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.BUTTON]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

SCHEMA_BACKUP_BASE = vol.Schema(
    {
        vol.Optional(ATTR_NAME): vol.Any(None, cv.string),
        vol.Optional(ATTR_PASSWORD): vol.Any(None, cv.string),
        vol.Optional(ATTR_KEEP_DAYS): vol.Any(None, vol.Coerce(float)),
        vol.Optional(ATTR_DOWNLOAD_PATH): vol.All(cv.ensure_list, [cv.isdir]),
        vol.Optional(ATTR_ENCRYPTED, default=False): cv.boolean,
        vol.Optional(ATTR_EXCLUDE_DATABASE, default=False): cv.boolean,
        vol.Optional(ATTR_COMPRESSED, default=True): cv.boolean,
        vol.Optional(ATTR_LOCATION): vol.All(
            cv.string, lambda v: None if v == "/backup" else v
        ),
    },
)

SCHEMA_LIST_STRING = vol.All(cv.ensure_list, [cv.string])

SCHEMA_ADDONS_FOLDERS = {
    vol.Optional(ATTR_FOLDERS, default=[]): SCHEMA_LIST_STRING,
    vol.Optional(ATTR_ADDONS, default=[]): SCHEMA_LIST_STRING,
}

SCHEMA_BACKUP_FULL = SCHEMA_BACKUP_BASE.extend(
    {vol.Optional(ATTR_EXCLUDE): SCHEMA_ADDONS_FOLDERS}
)

SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_BASE.extend(SCHEMA_ADDONS_FOLDERS)

SCHEMA_BACKUP = vol.Any(
    SCHEMA_BACKUP_BASE.extend(
        {
            vol.Optional(ATTR_INCLUDE): SCHEMA_ADDONS_FOLDERS,
            vol.Optional(ATTR_EXCLUDE): SCHEMA_ADDONS_FOLDERS,
        }
    ),
    SCHEMA_BACKUP_BASE.extend(
        {
            vol.Optional(ATTR_INCLUDE_ADDONS): SCHEMA_LIST_STRING,
            vol.Optional(ATTR_INCLUDE_FOLDERS): SCHEMA_LIST_STRING,
            vol.Optional(ATTR_EXCLUDE_ADDONS): SCHEMA_LIST_STRING,
            vol.Optional(ATTR_EXCLUDE_FOLDERS): SCHEMA_LIST_STRING,
        }
    ),
)

MAP_SERVICES = {
    SERVICE_BACKUP: SCHEMA_BACKUP,
    SERVICE_BACKUP_FULL: SCHEMA_BACKUP_FULL,
    SERVICE_BACKUP_PARTIAL: SCHEMA_BACKUP_PARTIAL,
    SERVICE_PURGE: None,
}


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
    """Set up Auto Backup from a config entry."""
    _LOGGER.info("Setting up Auto Backup config entry %s", entry.entry_id)

    # check backup integration or supervisor is available
    if not is_hassio(hass) and not is_backup(hass):
        _LOGGER.error(
            "You must be running Home Assistant Supervised or have the 'backup' integration enabled."
        )
        return False

    options = {
        CONF_AUTO_PURGE: entry.options.get(CONF_AUTO_PURGE, True),
        CONF_BACKUP_TIMEOUT: entry.options.get(
            CONF_BACKUP_TIMEOUT, DEFAULT_BACKUP_TIMEOUT
        ),
    }

    if is_hassio(hass):
        handler = SupervisorHandler(getenv("SUPERVISOR"), async_get_clientsession(hass))
    else:
        handler = BackupHandler(hass, hass.data[DATA_MANAGER])

    auto_backup = AutoBackup(hass, options, handler)
    hass.data[DATA_AUTO_BACKUP] = auto_backup
    entry.async_on_unload(entry.add_update_listener(auto_backup.update_listener))

    await auto_backup.load_snapshots_expiry()

    ### REGISTER SERVICES ###
    async def async_service_handler(call: ServiceCall):
        """Handle Auto Backup service calls."""
        if call.service == SERVICE_PURGE:
            await auto_backup.purge_backups()
        else:
            data = call.data.copy()
            if call.service == SERVICE_BACKUP_PARTIAL:
                data[ATTR_INCLUDE] = {
                    ATTR_FOLDERS: data.pop(ATTR_FOLDERS, []),
                    ATTR_ADDONS: data.pop(ATTR_ADDONS, []),
                }
            elif call.service == SERVICE_BACKUP:
                if ATTR_INCLUDE_ADDONS in data or ATTR_INCLUDE_FOLDERS in data:
                    data[ATTR_INCLUDE] = {
                        ATTR_FOLDERS: data.pop(ATTR_INCLUDE_FOLDERS, []),
                        ATTR_ADDONS: data.pop(ATTR_INCLUDE_ADDONS, []),
                    }
                if ATTR_EXCLUDE_ADDONS in data or ATTR_EXCLUDE_FOLDERS in data:
                    data[ATTR_EXCLUDE] = {
                        ATTR_FOLDERS: data.pop(ATTR_EXCLUDE_FOLDERS, []),
                        ATTR_ADDONS: data.pop(ATTR_EXCLUDE_ADDONS, []),
                    }

            await auto_backup.async_create_backup(data)

    for service, schema in MAP_SERVICES.items():
        hass.services.async_register(DOMAIN, service, async_service_handler, schema)

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
    """Unload a config entry."""
    for service in MAP_SERVICES.keys():
        hass.services.async_remove(DOMAIN, service)

    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)