From 7b748a0f0b610aaf367314f14d66e5958ef8a6a7 Mon Sep 17 00:00:00 2001 From: bahmcloud Date: Wed, 14 Jan 2026 18:47:12 +0000 Subject: [PATCH] custom_components/bahmcloud_store/store.py aktualisiert --- custom_components/bahmcloud_store/store.py | 64 +++++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/custom_components/bahmcloud_store/store.py b/custom_components/bahmcloud_store/store.py index b7d9e92..b7d41d5 100644 --- a/custom_components/bahmcloud_store/store.py +++ b/custom_components/bahmcloud_store/store.py @@ -35,7 +35,7 @@ class StoreConfig: class Package: id: str name: str - type: str + type: str # "integration" | "store" domain: str repo: str owner: str @@ -76,12 +76,19 @@ class BahmcloudStore: u = urlparse(repo_url.rstrip("/")) return f"{u.scheme}://{u.netloc}" + @staticmethod + def _raw_manifest_url(repo: str, branch: str, source_path: str) -> str: + # Example: + # https://git.bahmcloud.de/bahmcloud/easy_proxmox/raw/branch/main/custom_components/easy_proxmox/manifest.json + return f"{repo.rstrip('/')}/raw/branch/{branch}/{source_path.rstrip('/')}/manifest.json" + async def _fetch_latest_version(self, pkg: Package) -> tuple[str | None, str | None]: """ Returns (latest_version, release_url) Strategy: 1) releases/latest -> tag_name 2) tags?limit=1 -> first tag name + 3) fallback: read manifest.json from repo (version field) """ session = async_get_clientsession(self.hass) base = self._base_from_repo(pkg.repo) @@ -94,7 +101,8 @@ class BahmcloudStore: data = await resp.json() tag = data.get("tag_name") html_url = data.get("html_url") - return (str(tag) if tag else None, str(html_url) if html_url else None) + if tag: + return (str(tag), str(html_url) if html_url else None) except Exception: pass @@ -106,7 +114,21 @@ class BahmcloudStore: tags = await resp.json() if tags and isinstance(tags, list): name = tags[0].get("name") - return (str(name) if name else None, None) + if name: + return (str(name), None) + except Exception: + pass + + # 3) fallback: manifest.json version from repo + try: + manifest_url = self._raw_manifest_url(pkg.repo, pkg.branch, pkg.source_path) + async with session.get(manifest_url, timeout=20) as resp: + if resp.status == 200: + text = await resp.text() + data = json.loads(text) + ver = data.get("version") + if ver: + return (str(ver), None) except Exception: pass @@ -198,6 +220,9 @@ class BahmcloudStore: shutil.rmtree(target) shutil.copytree(src, target) + # Nach Installation: Entities neu aufbauen (damit es als Update auftaucht) + self.signal_entities_updated() + persistent_notification.async_create( self.hass, ( @@ -228,7 +253,7 @@ class BahmcloudStore: async def register_http_views(self) -> None: """Register HTTP views for static panel assets and JSON API.""" self.hass.http.register_view(_StaticView()) - self.hass.http.register_view(_APIListView(self)) + self.hass.http.register_view(_APIView(self)) class _StaticView(HomeAssistantView): @@ -236,12 +261,6 @@ class _StaticView(HomeAssistantView): IMPORTANT: Custom Panel JS modules are loaded WITHOUT Authorization headers. Therefore static panel assets must be publicly accessible (no auth). - - Serves: - /api/bahmcloud_store_static/index.html - /api/bahmcloud_store_static/panel.js - /api/bahmcloud_store_static/app.js - /api/bahmcloud_store_static/styles.css """ requires_auth = False name = "bahmcloud_store_static" @@ -254,7 +273,6 @@ class _StaticView(HomeAssistantView): f = (base / path).resolve() - # Prevent path traversal if not str(f).startswith(str(base)) or not f.exists() or not f.is_file(): return web.Response(status=404, text="Not found") @@ -269,10 +287,11 @@ class _StaticView(HomeAssistantView): return web.Response(body=f.read_bytes(), content_type="application/octet-stream") -class _APIListView(HomeAssistantView): +class _APIView(HomeAssistantView): """ - Store API MUST stay protected. - UI loads data via fetch() with HA auth handled by frontend. + Auth-protected API: + GET /api/bahmcloud_store -> list packages + POST /api/bahmcloud_store {op:...} -> install/update a package """ requires_auth = True name = "bahmcloud_store_api" @@ -300,3 +319,20 @@ class _APIListView(HomeAssistantView): } ) return self.json({"packages": items, "store_url": self.store.config.store_url}) + + async def post(self, request): + data = await request.json() + op = data.get("op") + package_id = data.get("package_id") + + if op not in ("install", "update"): + return self.json({"error": "unknown op"}, status_code=400) + if not package_id: + return self.json({"error": "package_id missing"}, status_code=400) + + pkg = self.store.packages.get(package_id) + if not pkg: + return self.json({"error": "unknown package_id"}, status_code=404) + + await self.store.install_from_zip(pkg) + return self.json({"ok": True})