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 ...
+
+
+
+
+
+
+ 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