diff --git a/homeassistant/INSTALL.md b/homeassistant/INSTALL.md index 9a89efa..96f1e5d 100644 --- a/homeassistant/INSTALL.md +++ b/homeassistant/INSTALL.md @@ -34,6 +34,45 @@ Zeitaufwand: ca. 30–40 Minuten, größtenteils Warten auf Neustarts. --- + +## Der schnelle Weg: install.sh + +Für eine neue Instanz gibt es ein Skript, das die Schritte 1 bis 3 und 5 +zusammen erledigt. Am einfachsten direkt auf der HA-Instanz im Add-on +**Terminal & SSH**: + +```bash +bash <(curl -fsSL https://gitea.nothaft.cloud/paul/audi-app/raw/branch/main/homeassistant/install.sh) +``` + +Liegt das Repository privat (Standard), braucht der Aufruf ein Zugangstoken: + +```bash +curl -fsSL -H "Authorization: token " \ + https://gitea.nothaft.cloud/paul/audi-app/raw/branch/main/homeassistant/install.sh -o install.sh +bash install.sh --repo https://:@gitea.nothaft.cloud/paul/audi-app.git +``` + +Alternativ von einem Rechner aus gegen ein eingebundenes config-Verzeichnis: + +```bash +./install.sh --ziel /Volumes/config --von ~/Development/audi-app +``` + +Das Skript installiert pyscript, spielt Backend und Oberfläche ein, legt das +Fahrzeugprofil aus der Vorlage an (**vorhandene Daten bleiben unangetastet**), +trägt den Abschnitt in die `configuration.yaml` ein — mit Sicherungskopie und +so, dass ein zweiter Lauf ihn ersetzt statt anhängt — und verdrahtet die +Selbstaktualisierung, damit „Update suchen" in der App funktioniert. +`--hilfe` zeigt alle Optionen. + +Danach bleiben nur noch drei Dinge, alle in der Weboberfläche: neu starten, +Sensoren zuordnen (Schritt 4 unten), Fahrzeugdaten eintragen. + +Wer lieber jeden Schritt selbst nachvollzieht, folgt der ausführlichen +Anleitung ab hier. + + ## Schritt 1 — pyscript installieren 1. In Home Assistant: **HACS → Integrationen → Explore & Download Repositories** diff --git a/homeassistant/install.sh b/homeassistant/install.sh new file mode 100755 index 0000000..1b117a4 --- /dev/null +++ b/homeassistant/install.sh @@ -0,0 +1,293 @@ +#!/usr/bin/env bash +# +# DataMetric360 — Installation in Home Assistant +# +# Richtet das Panel samt Backend auf einer Home-Assistant-Instanz ein und +# verdrahtet die Selbstaktualisierung, damit danach alles Weitere über die +# Weboberfläche läuft: Sensoren zuordnen, nach Updates suchen, einspielen. +# +# Wo ausführen: +# Am einfachsten direkt auf der HA-Instanz, im Add-on "Terminal & SSH": +# bash <(curl -fsSL ) +# Alternativ von einem Rechner aus gegen ein eingebundenes config-Verzeichnis: +# ./install.sh --ziel /Volumes/config +# +# Mehrfach ausführbar: bereits vorhandene Fahrzeugdaten werden nie +# überschrieben, und der Abschnitt in der configuration.yaml wird beim +# erneuten Lauf ersetzt statt ein zweites Mal angehängt. +# +set -euo pipefail + +# ------------------------------------------------------------ Voreinstellungen + +REPO_STANDARD="https://gitea.nothaft.cloud/paul/audi-app.git" +BRANCH="main" +ZIEL="/config" +QUELLE="" +TOKEN="" +REPO="$REPO_STANDARD" +PYSCRIPT_INSTALLIEREN="ja" +INTERAKTIV="ja" + +MARKE_START="# >>> DataMetric360 (von install.sh verwaltet) >>>" +MARKE_ENDE="# <<< DataMetric360 <<<" + +# ------------------------------------------------------------------- Ausgabe + +rot() { printf '\033[31m%s\033[0m\n' "$*"; } +gruen(){ printf '\033[32m%s\033[0m\n' "$*"; } +grau() { printf '\033[90m%s\033[0m\n' "$*"; } +schritt(){ printf '\n\033[1m==> %s\033[0m\n' "$*"; } +fehler(){ rot "FEHLER: $*"; exit 1; } + +hilfe() { + cat <<'ENDE' +DataMetric360 — Installation in Home Assistant + + --ziel config-Verzeichnis von Home Assistant (Standard: /config) + --von lokale Arbeitskopie benutzen statt zu klonen + --repo Repository zum Klonen und für die Selbstaktualisierung + --branch Branch (Standard: main) + --token langlebiges Zugriffstoken für die Kilometerstand-Historie + --ohne-pyscript pyscript nicht mitinstallieren (wenn schon vorhanden) + --still keine Rückfragen stellen (für unbeaufsichtigte Läufe) + --hilfe diese Übersicht + +Beispiele + bash install.sh # auf der HA-Instanz, klont selbst + bash install.sh --von ~/audi-app # aus einer lokalen Arbeitskopie + bash install.sh --ziel /Volumes/config # gegen ein eingebundenes Laufwerk +ENDE +} + +while [ $# -gt 0 ]; do + case "$1" in + --ziel) ZIEL="${2:-}"; shift 2 ;; + --von) QUELLE="${2:-}"; shift 2 ;; + --repo) REPO="${2:-}"; shift 2 ;; + --branch) BRANCH="${2:-}"; shift 2 ;; + --token) TOKEN="${2:-}"; shift 2 ;; + --ohne-pyscript) PYSCRIPT_INSTALLIEREN="nein"; shift ;; + --still) INTERAKTIV="nein"; shift ;; + --hilfe|-h) hilfe; exit 0 ;; + *) fehler "Unbekannte Option: $1 (--hilfe zeigt alle)" ;; + esac +done + +# --------------------------------------------------------------- Vorprüfungen + +schritt "Umgebung prüfen" + +for werkzeug in python3 curl unzip; do + command -v "$werkzeug" >/dev/null 2>&1 || fehler "$werkzeug wird gebraucht, ist aber nicht vorhanden." +done + +[ -d "$ZIEL" ] || fehler "Das Zielverzeichnis $ZIEL gibt es nicht. Mit --ziel den richtigen Pfad angeben." +[ -w "$ZIEL" ] || fehler "Keine Schreibrechte auf $ZIEL." + +if [ ! -f "$ZIEL/configuration.yaml" ]; then + rot "In $ZIEL liegt keine configuration.yaml." + fehler "Das sieht nicht nach einem Home-Assistant-Verzeichnis aus. Pfad mit --ziel prüfen." +fi +grau " Ziel: $ZIEL" + +# ------------------------------------------------------------ Quelle besorgen + +AUFRAEUMEN="" +saeubern() { [ -n "$AUFRAEUMEN" ] && rm -rf "$AUFRAEUMEN" || true; } +trap saeubern EXIT + +if [ -n "$QUELLE" ]; then + schritt "Lokale Arbeitskopie benutzen" + [ -d "$QUELLE/homeassistant" ] || fehler "In $QUELLE gibt es keinen Ordner homeassistant/." + HA_QUELLE="$QUELLE/homeassistant" + grau " $HA_QUELLE" +else + schritt "Projekt herunterladen" + command -v git >/dev/null 2>&1 || fehler "git wird zum Klonen gebraucht (oder --von benutzen)." + AUFRAEUMEN="$(mktemp -d)" + if ! git clone --depth 1 --branch "$BRANCH" "$REPO" "$AUFRAEUMEN/quelle" >/dev/null 2>&1; then + rot "Klonen von $REPO fehlgeschlagen." + grau " Bei einem privaten Repository ein Zugangstoken in die URL aufnehmen, etwa:" + grau " --repo https://:@gitea.example/paul/audi-app.git" + exit 1 + fi + HA_QUELLE="$AUFRAEUMEN/quelle/homeassistant" + grau " $REPO ($BRANCH)" +fi + +[ -d "$HA_QUELLE/pyscript" ] || fehler "Im Projekt fehlt homeassistant/pyscript/." + +# ------------------------------------------------------------------- pyscript + +if [ "$PYSCRIPT_INSTALLIEREN" = "ja" ]; then + if [ -d "$ZIEL/custom_components/pyscript" ]; then + schritt "pyscript ist bereits vorhanden" + grau " $ZIEL/custom_components/pyscript" + else + schritt "pyscript installieren" + VERSION="$(curl -fsSL https://api.github.com/repos/custom-components/pyscript/releases/latest \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])' 2>/dev/null || true)" + [ -n "$VERSION" ] || fehler "Konnte die neueste pyscript-Version nicht ermitteln (kein Internet?)." + TMPPY="$(mktemp -d)" + curl -fsSL -o "$TMPPY/pyscript.zip" \ + "https://github.com/custom-components/pyscript/archive/refs/tags/${VERSION}.zip" + unzip -oq "$TMPPY/pyscript.zip" -d "$TMPPY" + mkdir -p "$ZIEL/custom_components" + cp -r "$TMPPY/pyscript-${VERSION}/custom_components/pyscript" "$ZIEL/custom_components/" + rm -rf "$TMPPY" + gruen " pyscript $VERSION installiert" + fi +fi + +# --------------------------------------------------------- Dateien einspielen + +schritt "Backend und Oberfläche einspielen" + +mkdir -p "$ZIEL/pyscript" "$ZIEL/www" "$ZIEL/audi_dashboard" + +# pyscript und www sind Code - vollständig ersetzen. +cp -r "$HA_QUELLE/pyscript/." "$ZIEL/pyscript/" +cp -r "$HA_QUELLE/www/." "$ZIEL/www/" +grau " pyscript/ und www/ aktualisiert" + +# Der Belegleser liegt in data/, ist aber Code (belegverarbeitung.py ruft ihn +# als Unterprozess auf) - deshalb hier mit, anders als die Datenbestände. +cp "$HA_QUELLE/data/shell_beleg_parser.py" "$ZIEL/audi_dashboard/shell_beleg_parser.py" + +# Datenbestände: nur anlegen, was fehlt. Vorhandenes nie überschreiben. +if [ ! -f "$ZIEL/audi_dashboard/fahrzeugprofil.json" ]; then + cp "$HA_QUELLE/data/fahrzeugprofil.example.json" "$ZIEL/audi_dashboard/fahrzeugprofil.json" + gruen " Fahrzeugprofil aus der Vorlage angelegt" +else + grau " Fahrzeugprofil vorhanden - unangetastet" +fi +for datei in fahrten.jsonl tankvorgaenge.jsonl; do + [ -f "$ZIEL/audi_dashboard/$datei" ] || : > "$ZIEL/audi_dashboard/$datei" +done +mkdir -p "$ZIEL/audi_dashboard/belege" + +# --------------------------------------------------- Selbstaktualisierung + +schritt "Selbstaktualisierung verdrahten" + +# UPDATE_REPO_URL im ausgelieferten einstellungen.py setzen, damit +# "Update suchen" in den Einstellungen der App funktioniert. +python3 - "$ZIEL/pyscript/modules/einstellungen.py" "$REPO" "$BRANCH" <<'PYENDE' +import re, sys +pfad, repo, branch = sys.argv[1], sys.argv[2], sys.argv[3] +with open(pfad, encoding="utf-8") as f: + inhalt = f.read() +inhalt = re.sub(r'^UPDATE_REPO_URL\s*=.*$', f'UPDATE_REPO_URL = "{repo}"', inhalt, count=1, flags=re.M) +inhalt = re.sub(r'^UPDATE_BRANCH\s*=.*$', f'UPDATE_BRANCH = "{branch}"', inhalt, count=1, flags=re.M) +with open(pfad, "w", encoding="utf-8") as f: + f.write(inhalt) +PYENDE +grau " Updatequelle: $REPO ($BRANCH)" +grau " Danach in der App unter Einstellungen → Version → \"Update suchen\"" + +# ---------------------------------------------------------- configuration.yaml + +schritt "configuration.yaml ergänzen" + +KONFIG="$ZIEL/configuration.yaml" +SICHERUNG="$KONFIG.vor-datametric360-$(date +%Y%m%d_%H%M%S)" +cp "$KONFIG" "$SICHERUNG" +grau " Sicherung: $(basename "$SICHERUNG")" + +python3 - "$KONFIG" "$MARKE_START" "$MARKE_ENDE" <<'PYENDE' +import re, sys +pfad, start, ende = sys.argv[1], sys.argv[2], sys.argv[3] +with open(pfad, encoding="utf-8") as f: + inhalt = f.read() + +block = f"""{start} +# Von install.sh eingefügt. Aenderungen innerhalb dieses Abschnitts werden bei +# der naechsten Installation ersetzt - Eigenes bitte ausserhalb notieren. +pyscript: + allow_all_imports: true + +panel_custom: + - name: audi-dashboard-panel + sidebar_title: Mein Audi + sidebar_icon: mdi:car-sports + module_url: /local/audi-dashboard-panel.js?v=1 + embed_iframe: false + trust_external_script: false +{ende}""" + +muster = re.compile(re.escape(start) + r".*?" + re.escape(ende), re.S) +if muster.search(inhalt): + inhalt = muster.sub(block, inhalt) + print(" Abschnitt ersetzt") +else: + for schluessel in ("pyscript:", "panel_custom:"): + if re.search(rf"^{schluessel}", inhalt, re.M): + print(f" ACHTUNG: '{schluessel}' steht bereits ausserhalb des verwalteten Abschnitts.") + print(" Der Abschnitt wird trotzdem angehaengt - Home Assistant meldet dann") + print(" einen doppelten Schluessel. Bitte den alten Eintrag von Hand entfernen.") + if not inhalt.endswith("\n"): + inhalt += "\n" + inhalt += "\n" + block + "\n" + print(" Abschnitt angehaengt") + +with open(pfad, "w", encoding="utf-8") as f: + f.write(inhalt) +PYENDE + +# ------------------------------------------------------------------- Token + +schritt "Zugriffstoken für die Kilometerstand-Historie" + +TOKENDATEI="$ZIEL/audi_dashboard/ha_token.txt" +if [ -n "$TOKEN" ]; then + printf '%s' "$TOKEN" > "$TOKENDATEI" + chmod 600 "$TOKENDATEI" 2>/dev/null || true + gruen " Token gespeichert" +elif [ -s "$TOKENDATEI" ]; then + grau " Vorhandenes Token bleibt unangetastet" +elif [ "$INTERAKTIV" = "ja" ] && [ -t 0 ]; then + echo " Der Fahrtabschluss liest den Kilometerstand aus dem Verlauf und braucht dafür" + echo " ein langlebiges Zugriffstoken (Profil → Sicherheit → Langlebige Zugriffstoken)." + echo " Leer lassen und später nachtragen ist möglich - Fahrten bleiben dann ohne Strecke." + printf " Token (Eingabe bleibt verborgen): " + read -rs EINGABE || EINGABE="" + echo + if [ -n "$EINGABE" ]; then + printf '%s' "$EINGABE" > "$TOKENDATEI" + chmod 600 "$TOKENDATEI" 2>/dev/null || true + gruen " Token gespeichert" + else + rot " Übersprungen - später in $TOKENDATEI ablegen." + fi +else + rot " Kein Token angegeben. Später in $TOKENDATEI ablegen," + rot " sonst bleiben Fahrten ohne Streckenangabe." +fi + +# ------------------------------------------------------------------ Abschluss + +schritt "Fertig" +cat <