diff --git a/README.md b/README.md index 11f990d..265c2a0 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ uvicorn app.main:app --reload ``` 4. Erreichbar unter: +- `http://127.0.0.1:8000/` - lokales Dashboard - `http://127.0.0.1:8000/health` - Health-Check - `http://127.0.0.1:8000/docs/` - OpenAPI-Dokumentation - `http://127.0.0.1:8000/v1/entities` - Home-Assistant-Entities @@ -72,6 +73,17 @@ dem Netz muss ein authentifizierender Reverse Proxy vorgeschaltet werden. Niemals Administrator-Tokens oder Passwörter eintragen. `.env` gehört nicht ins Versionskontrollsystem. +### Home-Assistant-Add-on + +Das Repository ist zugleich ein Home-Assistant-Add-on-Repository. In Home Assistant +unter **Einstellungen → Add-ons → Add-on-Shop → Repositories** diese URL eintragen: + +`http://192.168.6.31:3000/pino/sillyhome-next` + +Danach **SillyHome Next** installieren und starten. Das Dashboard wird per Ingress +geöffnet. Das Add-on nutzt die Supervisor-API nur lesend; Automation-Entwürfe werden +lokal gespeichert und niemals automatisch ausgeführt. + ### Tests ```bash pytest diff --git a/addon/Dockerfile b/addon/Dockerfile new file mode 100644 index 0000000..13d2962 --- /dev/null +++ b/addon/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.13-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends git \ + && git clone --depth 1 --branch main \ + http://192.168.6.31:3000/pino/sillyhome-next.git /app \ + && python -m pip install --upgrade pip \ + && python -m pip install /app \ + && rm -rf /var/lib/apt/lists/* /app/.git + +COPY run.sh /run.sh +RUN chmod 0755 /run.sh + +EXPOSE 8000 +CMD ["/run.sh"] diff --git a/addon/config.yaml b/addon/config.yaml new file mode 100644 index 0000000..d459da5 --- /dev/null +++ b/addon/config.yaml @@ -0,0 +1,21 @@ +name: SillyHome Next +version: "0.3.0" +slug: sillyhome_next +description: Lokale HA-Analyse, Vorhersagen und sichere Automation-Entwürfe +url: http://192.168.6.31:3000/pino/sillyhome-next +arch: + - amd64 +startup: application +boot: auto +init: false +ingress: true +ingress_port: 8000 +panel_icon: mdi:home-analytics +homeassistant_api: true +hassio_api: false +auth_api: false +options: {} +schema: {} +map: + - type: addon_config + read_only: false diff --git a/addon/run.sh b/addon/run.sh new file mode 100644 index 0000000..365d824 --- /dev/null +++ b/addon/run.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu + +export SILLYHOME_HA_URL="${SILLYHOME_HA_URL:-http://supervisor/core}" +export SILLYHOME_HA_TOKEN="${SILLYHOME_HA_TOKEN:-${SUPERVISOR_TOKEN:-}}" +export SILLYHOME_MODEL_STORE=/data/models +export SILLYHOME_AUTOMATION_STORE=/data/automations + +mkdir -p "$SILLYHOME_MODEL_STORE" "$SILLYHOME_AUTOMATION_STORE" +exec uvicorn app.main:app --app-dir /app --host 0.0.0.0 --port 8000 \ + --proxy-headers --forwarded-allow-ips='*' diff --git a/app/main.py b/app/main.py index 49423e5..b8ec755 100644 --- a/app/main.py +++ b/app/main.py @@ -1,8 +1,11 @@ from contextlib import asynccontextmanager from collections.abc import AsyncIterator +from pathlib import Path from typing import cast from fastapi import FastAPI +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles from app.api.v1.entities import router as entities_router from app.api.v1.automations import router as automations_router @@ -41,7 +44,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app = FastAPI( title="SillyHome Next API", description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.", - version="0.2.0", + version="0.3.0", lifespan=lifespan, ) app.state.settings = load_settings() @@ -50,6 +53,9 @@ app.include_router(entities_router) app.include_router(automations_router) init_ml_routes(app, model_store=app.state.settings.model_store) +STATIC_DIR = Path(__file__).with_name("static") +app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") + @app.get("/health") def health() -> dict[str, str]: @@ -57,5 +63,5 @@ def health() -> dict[str, str]: @app.get("/") -def root() -> dict[str, str]: - return {"service": "sillyhome-next", "docs": "/docs"} +def root() -> FileResponse: + return FileResponse(STATIC_DIR / "index.html") diff --git a/app/static/index.html b/app/static/index.html new file mode 100644 index 0000000..69a0e00 --- /dev/null +++ b/app/static/index.html @@ -0,0 +1,149 @@ + + + + + + SillyHome Next + + + +
+

SillyHome Next

+

Lokale Home-Assistant-Analyse, Vorhersagen und kontrollierte Automation-Entwürfe.

+

Sicherheitsmodus: Entwürfe werden niemals automatisch in Home Assistant ausgeführt.

+
+
+
+

Systemstatus

+
Prüfung läuft ...
+ +
+
+

Entity Discovery

+ + + +
Noch nicht geladen.
+
+
+

Modell trainieren

+ + + + + +
Bereit.
+
+
+

Vorhersage

+ + + + + +
Bereit.
+
+
+

Automation-Entwurf

+

Der Entwurf muss explizit freigegeben werden. Auch danach wird nur YAML exportiert, nichts geschaltet.

+
+
+
+
+
+
+
+ + +
+
+
+ + + diff --git a/pyproject.toml b/pyproject.toml index bc1f262..8e3455e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sillyhome-next" -version = "0.2.0" +version = "0.3.0" description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant" requires-python = ">=3.11" dependencies = [ diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..749e6b3 --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: SillyHome Next Add-ons +url: http://192.168.6.31:3000/pino/sillyhome-next +maintainer: Pino diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py new file mode 100644 index 0000000..094810d --- /dev/null +++ b/tests/test_dashboard.py @@ -0,0 +1,12 @@ +from fastapi.testclient import TestClient + +from app.main import app + + +def test_dashboard_is_served_at_root() -> None: + with TestClient(app) as client: + response = client.get("/") + + assert response.status_code == 200 + assert "SillyHome Next" in response.text + assert "Automation-Entwurf" in response.text