diff --git a/.idea/changes.md b/.idea/changes.md
index 3bdd5c9..503665f 100644
--- a/.idea/changes.md
+++ b/.idea/changes.md
@@ -21,6 +21,7 @@
- Bumped the integration version from `0.7.4` to `0.7.5` and added the `0.7.5` release entry to `CHANGELOG.md` for blueprint support and the documentation refresh.
- Fixed blueprint update backups: blueprint file updates now create content backups before overwrite and can roll back copied blueprint files if installation fails.
- Kept the no-restart behavior for blueprints, because blueprint deployment does not normally require a Home Assistant restart.
+- Restored blueprint backup/restore availability in the UI and backend: the restore button is visible again for blueprint installs, blueprint backups can be listed, and blueprint content can now be restored from backup without forcing a restart.
### Documented
- Captured the verified project identity from the repository and README files: Bahmcloud Store is a Home Assistant custom integration intended to behave like a provider-neutral store for custom integrations, similar to HACS but broader than GitHub-only workflows.
diff --git a/custom_components/bahmcloud_store/core.py b/custom_components/bahmcloud_store/core.py
index 335c58d..381c87c 100644
--- a/custom_components/bahmcloud_store/core.py
+++ b/custom_components/bahmcloud_store/core.py
@@ -1536,6 +1536,30 @@ class BCSCore:
domains of the repository.
"""
inst = self.get_installed(repo_id) or {}
+ install_type = str(inst.get("install_type") or "integration").strip() or "integration"
+ if install_type == "blueprint":
+ repo_root = self._backup_root / "_content" / self._backup_repo_key(repo_id)
+
+ def _list_content() -> list[str]:
+ if not repo_root.exists() or not repo_root.is_dir():
+ return []
+ ids = [p.name for p in repo_root.iterdir() if p.is_dir()]
+ ids.sort(reverse=True)
+ return ids
+
+ ids = await self.hass.async_add_executor_job(_list_content)
+ items: list[dict[str, Any]] = []
+ for bid in ids[: self._backup_keep_per_domain]:
+ label = self._format_backup_id(bid)
+ meta = await self._read_content_backup_meta(repo_id, bid)
+ ver = None
+ if isinstance(meta, dict):
+ ver = meta.get("installed_version") or meta.get("ref")
+ if ver:
+ label = f"{label} ({ver})"
+ items.append({"id": bid, "label": label, "complete": True, "domains": [], "installed_version": str(ver) if ver else None})
+ return items
+
domains = inst.get("domains") or []
if not isinstance(domains, list) or not domains:
return []
@@ -1587,6 +1611,53 @@ class BCSCore:
if not inst:
raise BCSInstallError("Repository is not installed")
+ install_type = str(getattr(inst, "install_type", "integration") or "integration").strip() or "integration"
+ if install_type == "blueprint":
+ backup_path = self._backup_root / "_content" / self._backup_repo_key(repo_id) / backup_id
+ if not backup_path.exists() or not backup_path.is_dir():
+ raise BCSInstallError("Selected backup is not available")
+
+ installed_paths = [str(p).strip() for p in (getattr(inst, "installed_paths", None) or []) if str(p).strip()]
+
+ async with self._install_lock:
+ _LOGGER.info("BCS restore started: repo_id=%s backup_id=%s type=blueprint", repo_id, backup_id)
+
+ try:
+ await self._backup_paths(repo_id, installed_paths)
+ except Exception:
+ _LOGGER.debug("BCS pre-restore content backup failed for repo_id=%s", repo_id, exc_info=True)
+
+ await self._restore_paths_from_backup(backup_path, remove_targets=installed_paths)
+
+ restored_meta = await self._read_content_backup_meta(repo_id, backup_id)
+ restored_version: str | None = None
+ if isinstance(restored_meta, dict):
+ rv = restored_meta.get("installed_version") or restored_meta.get("ref")
+ if rv is not None and str(rv).strip():
+ restored_version = str(rv).strip()
+ if not restored_version:
+ restored_version = f"restored:{backup_id}"
+
+ repo = self.get_repo(repo_id)
+ repo_url = getattr(repo, "url", None) or ""
+
+ await self.storage.set_installed_repo(
+ repo_id=repo_id,
+ url=repo_url,
+ domains=[],
+ installed_version=restored_version,
+ installed_manifest_version=None,
+ ref=restored_version,
+ install_type="blueprint",
+ installed_paths=installed_paths,
+ )
+
+ await self._refresh_installed_cache()
+ self.signal_updated()
+
+ _LOGGER.info("BCS restore complete: repo_id=%s backup_id=%s type=blueprint", repo_id, backup_id)
+ return {"ok": True, "repo_id": repo_id, "backup_id": backup_id, "domains": [], "installed_paths": installed_paths, "restored_version": restored_version, "restart_required": False}
+
domains = inst.get("domains") or []
if not isinstance(domains, list) or not domains:
raise BCSInstallError("No installed domains found")
@@ -1683,6 +1754,17 @@ class BCSCore:
except Exception:
return None
+ async def _read_content_backup_meta(self, repo_id: str, backup_id: str) -> dict[str, Any] | None:
+ try:
+ p = self._backup_root / "_content" / self._backup_repo_key(repo_id) / backup_id / BACKUP_META_FILENAME
+ if not p.exists():
+ return None
+ txt = await self.hass.async_add_executor_job(p.read_text, 'utf-8')
+ data = json.loads(txt)
+ return data if isinstance(data, dict) else None
+ except Exception:
+ return None
+
async def _read_backup_manifest_version(self, domain: str, backup_id: str) -> str | None:
"""Best-effort: read manifest.json version from a legacy backup (no metadata)."""
diff --git a/custom_components/bahmcloud_store/panel/panel.js b/custom_components/bahmcloud_store/panel/panel.js
index f3182a7..9a4cf56 100644
--- a/custom_components/bahmcloud_store/panel/panel.js
+++ b/custom_components/bahmcloud_store/panel/panel.js
@@ -1379,9 +1379,7 @@ class BahmcloudStorePanel extends HTMLElement {
const installBtn = ``;
const updateBtn = ``;
const uninstallBtn = ``;
- const restoreBtn = installType === "integration"
- ? ``
- : ``;
+ const restoreBtn = ``;
const favoriteBtn = ``;
const restartHint = this._restartRequired