5ff33c92d9
Bisher wurde nach dem manuell eingegebenen config-Pfad erst ein Erreichbarkeits-Test versucht (der ohne bestehende SMB-Sitzung fast immer scheitert) und erst danach nach Benutzername/Kennwort gefragt. Jetzt folgt die Abfrage direkt auf die Pfadeingabe. Die Anmeldelogik selbst ist dafür in die Funktion SambaAnmelden ausgelagert, die weiterhin auch als Rückfallebene für automatisch gefundene oder per -Ziel übergebene Pfade dient. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
295 lines
15 KiB
PowerShell
295 lines
15 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Installiert "Mein Audi" (DataMetric360) als Home-Assistant-Integration -
|
|
der Weg ohne HACS.
|
|
|
|
.BESCHREIBUNG
|
|
Kopiert genau einen Ordner:
|
|
|
|
custom_components\audi_dashboard\ -> <Ziel>\custom_components\
|
|
|
|
Mehr ist nicht nötig. Alles, was früher an dieser Stelle von Hand gemacht
|
|
werden musste, macht die Integration inzwischen selbst: sie meldet ihr Panel
|
|
an, liefert ihre Oberflächen-Dateien selbst aus, legt beim ersten Start ein
|
|
Fahrzeugprofil aus der Vorlage an und bringt pypdf über ihre manifest.json
|
|
mit, das Home Assistant beim ersten Laden nachinstalliert.
|
|
|
|
WOFÜR ES DIESES SKRIPT ÜBERHAUPT GIBT
|
|
--------------------------------------
|
|
HACS scheidet für dieses Repository aus, und zwar endgültig: laut eigener
|
|
Dokumentation (hacs.xyz/docs/faq/private_repositories) kann HACS
|
|
"grundsätzlich nicht" mit privaten GitHub-Repositories arbeiten - ohne
|
|
Ausnahme für Tokens oder verbundene Konten. Das Repository enthält die
|
|
Audi-Hausschrift und die Typenschilder, die nur für diese eine private
|
|
Installation lizenziert sind, und öffentlich machen ist deshalb keine
|
|
Option. Ein GitHub-Spiegel wurde deshalb 2026-08-24 wieder aufgegeben -
|
|
entwickelt wird ausschließlich auf der eigenen Gitea-Instanz.
|
|
|
|
Dieses Skript ist damit nicht die Rückfallebene, sondern der einzige Weg.
|
|
|
|
SICHERHEIT GEGENÜBER DER BESTEHENDEN HA-INSTALLATION
|
|
----------------------------------------------------
|
|
Die riskanteste Stelle der früheren Fassung gibt es nicht mehr: sie musste
|
|
die configuration.yaml ergänzen (pyscript:, panel_custom:), und eine kaputte
|
|
configuration.yaml ist der eine Weg, auf dem ein Installer Home Assistant am
|
|
Starten hindern kann. Diese Fassung fasst die Datei überhaupt nicht mehr an.
|
|
|
|
Was bleibt, ist nachprüfbar:
|
|
- Geschrieben wird ausschließlich in <Ziel>\custom_components\audi_dashboard.
|
|
Kein anderer Pfad wird angefasst - nicht .storage\, nicht
|
|
configuration.yaml, nicht automations.yaml, nicht www\, nicht
|
|
audi_dashboard\ (die Fahrzeugdaten).
|
|
- Entfernt wird nur der eigene Ordner, und nur, wenn darin eine
|
|
manifest.json mit "domain": "audi_dashboard" liegt. Ein fremder Ordner
|
|
unter diesem Namen führt zum Abbruch, nicht zum Löschen.
|
|
- Es startet Home Assistant nicht neu und greift nicht in den laufenden
|
|
Betrieb ein.
|
|
- -Pruefen zeigt den kompletten Ablauf, ohne irgendetwas zu schreiben.
|
|
Im Zweifel damit anfangen.
|
|
|
|
Warum der Zielordner vor dem Kopieren geleert wird (und nicht nur
|
|
überschrieben): bleibt eine Datei aus einer älteren Fassung liegen, die es
|
|
im neuen Stand nicht mehr gibt, lädt Home Assistant sie trotzdem mit. Das
|
|
ist genau der Fehler, den man hinterher am schwersten findet. HACS macht es
|
|
aus demselben Grund so.
|
|
|
|
Die Fahrzeugdaten liegen woanders (<Ziel>\audi_dashboard\) und werden von
|
|
diesem Skript nie berührt - ein Update kann sie deshalb gar nicht treffen.
|
|
|
|
.PARAMETER Ziel
|
|
config-Verzeichnis der HA-Instanz, z. B. "\\homeassistant.local\config"
|
|
(Samba-Add-on) oder ein lokal gemounteter Pfad. Ohne Angabe sucht das
|
|
Skript die üblichen Samba-Namen selbst ab.
|
|
|
|
.PARAMETER Pruefen
|
|
Nur anzeigen, was passieren würde - es wird nichts geschrieben.
|
|
|
|
.BEISPIEL
|
|
.\install.ps1
|
|
.\install.ps1 -Ziel "\\192.168.1.50\config"
|
|
.\install.ps1 -Pruefen
|
|
#>
|
|
param(
|
|
[string]$Ziel,
|
|
[switch]$Pruefen
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$hier = $PSScriptRoot
|
|
|
|
function Schritt($text) { Write-Host "`n$text" -ForegroundColor Cyan }
|
|
function Gut($text) { Write-Host " [ok] $text" -ForegroundColor Green }
|
|
function Info($text) { Write-Host " [info] $text" -ForegroundColor DarkGray }
|
|
function Warnung($text) { Write-Host " [!] $text" -ForegroundColor Yellow }
|
|
|
|
# Test-Path fragt nie nach einer Anmeldung: ohne eine bereits bestehende
|
|
# SMB-Sitzung zu diesem Server liefert es bei einem passwortgeschützten Share
|
|
# einfach "false" - ganz ohne Fehlermeldung oder Passwortabfrage. Diese
|
|
# Funktion baut die Sitzung selbst auf, statt das als manuellen Vorschritt
|
|
# vorauszusetzen.
|
|
function SambaAnmelden($pfad) {
|
|
$nutzer = Read-Host " Benutzername [homeassistant]"
|
|
if (-not $nutzer) { $nutzer = "homeassistant" }
|
|
$kennwortSicher = Read-Host " Kennwort" -AsSecureString
|
|
$kennwort = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
|
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($kennwortSicher))
|
|
|
|
# Eine tote Verbindung von einem vorherigen Versuch würde den Neuaufbau
|
|
# blockieren - leise entfernen, falls vorhanden. "Nichts zu löschen" ist
|
|
# dabei der Normalfall, meldet sich aber als Fehler - mit
|
|
# $ErrorActionPreference "Stop" würde das sonst das ganze Skript
|
|
# abbrechen, deshalb hier abgefangen statt nur umgeleitet.
|
|
try { & net use $pfad /delete /y *> $null } catch { }
|
|
|
|
& net use $pfad /user:$nutzer $kennwort
|
|
$kennwort = $null
|
|
}
|
|
|
|
$restliste = New-Object System.Collections.ArrayList
|
|
|
|
Write-Host ""
|
|
Write-Host " Mein Audi - Installation" -ForegroundColor White
|
|
Write-Host " ========================" -ForegroundColor White
|
|
if ($Pruefen) { Warnung "Pruefmodus - es wird nichts geschrieben." }
|
|
|
|
# ------------------------------------------------------------ Quelle finden
|
|
# Zwei Lagen, damit sowohl der Aufruf aus dem Projektordner heraus als auch
|
|
# ein separat weitergegebenes Paket funktioniert.
|
|
$quellKandidaten = @(
|
|
(Join-Path $hier "custom_components\audi_dashboard"),
|
|
(Join-Path $hier "..\..\custom_components\audi_dashboard")
|
|
)
|
|
$quelle = $null
|
|
foreach ($k in $quellKandidaten) {
|
|
if (Test-Path (Join-Path $k "manifest.json")) { $quelle = (Resolve-Path $k).Path; break }
|
|
}
|
|
if (-not $quelle) {
|
|
throw "custom_components\audi_dashboard nicht gefunden - dieses Skript gehört in den Projektordner (oder das Paket daneben)."
|
|
}
|
|
$version = (Get-Content (Join-Path $quelle "manifest.json") -Raw | ConvertFrom-Json).version
|
|
Gut "Quelle: $quelle (Version $version)"
|
|
|
|
# ------------------------------------------------ OTA-Bündel auf Stand prüfen
|
|
# Das Bündel für die iOS-App (frontend\app\bundle.json, gebaut von
|
|
# companion-app\scripts\ota-paket.ps1) trägt seine eigene, zum Bauzeitpunkt
|
|
# eingefrorene Versionsnummer. Ändert sich manifest.json's Version, ohne dass
|
|
# jemand "npm run ota" erneut laufen lässt, bekommt die App zwei
|
|
# widersprüchliche Signale gleichzeitig: die Hinweisleiste sagt "du bist
|
|
# veraltet" (vergleicht gegen die live gelesene manifest-Version), die
|
|
# Update-Seite sagt "du bist aktuell" (vergleicht gegen das eingefrorene, nun
|
|
# falsche Bündel) - und es gibt keinen Weg, das Update zu bekommen, bis jemand
|
|
# den Widerspruch bemerkt. Das ist kein Zustand, den ein Installer schweigend
|
|
# durchwinken sollte - hier ist der eine Ort, an dem jede Auslieferung
|
|
# durchläuft, also der einzige verlässliche Punkt für diese Prüfung.
|
|
#
|
|
# Ein fehlendes Bündel ist dagegen kein Fehler: OTA ist eine optionale
|
|
# Zusatzfunktion, nicht jede Installation liefert eins mit.
|
|
$buendelInfo = Join-Path $quelle "frontend\app\bundle.json"
|
|
if (Test-Path $buendelInfo) {
|
|
$buendelVersion = (Get-Content $buendelInfo -Raw | ConvertFrom-Json).version
|
|
if ($buendelVersion -ne $version) {
|
|
Warnung "OTA-Bündel (frontend\app\bundle.json) steht auf Version $buendelVersion, die Integration auf $version."
|
|
Write-Host " Die App-Update-Funktion würde bis zur Behebung falsche Ergebnisse zeigen." -ForegroundColor Yellow
|
|
[void]$restliste.Add("OTA-Bündel ist veraltet (Version $buendelVersion statt $version): in companion-app\ 'npm run ota' ausführen, dann dieses Skript erneut laufen lassen.")
|
|
} else {
|
|
Info "OTA-Bündel passt zur Integration (Version $buendelVersion)"
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------- Ziel finden
|
|
if (-not $Ziel) {
|
|
Schritt "Suche Home Assistant ..."
|
|
# Die Reihenfolge ist Absicht: der kurze Name antwortet in den meisten
|
|
# Heimnetzen schneller als der .local-Name über mDNS.
|
|
$kandidaten = @(
|
|
"\\homeassistant\config",
|
|
"\\homeassistant.local\config",
|
|
"\\hassio\config"
|
|
)
|
|
foreach ($k in $kandidaten) {
|
|
Info "probiere $k"
|
|
if (Test-Path $k) { $Ziel = $k; break }
|
|
}
|
|
if (-not $Ziel) {
|
|
Write-Host ""
|
|
Warnung "Keine HA-Instanz automatisch gefunden."
|
|
Write-Host " Bitte den config-Pfad angeben, z. B. \\192.168.1.50\config"
|
|
Write-Host " (Samba-Share-Add-on in HA muss laufen und verbunden sein.)"
|
|
Write-Host ""
|
|
$Ziel = Read-Host " config-Pfad"
|
|
if (-not $Ziel) { throw "Kein Ziel angegeben - Abbruch." }
|
|
|
|
# Direkt nach der Pfadeingabe fragen, nicht erst nach einem
|
|
# (ohne bestehende Sitzung fast immer erfolglosen) Erreichbarkeits-
|
|
# Test - siehe SambaAnmelden oben.
|
|
if ($Ziel -match '^\\\\') {
|
|
Write-Host ""
|
|
SambaAnmelden $Ziel
|
|
}
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path $Ziel)) {
|
|
# Automatisch gefundene oder per -Ziel übergebene Pfade durchlaufen
|
|
# SambaAnmelden oben nicht - hier derselbe Weg als Rückfallebene, falls
|
|
# auch für sie eine Anmeldung fehlt.
|
|
if ($Ziel -match '^\\\\') {
|
|
Warnung "Kein Zugriff auf $Ziel - vermutlich fehlt die Anmeldung am Samba-Share."
|
|
Write-Host ""
|
|
SambaAnmelden $Ziel
|
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path $Ziel)) {
|
|
throw "Ziel nicht erreichbar: $Ziel - Anmeldung fehlgeschlagen oder Pfad falsch."
|
|
}
|
|
Gut "Angemeldet."
|
|
} else {
|
|
throw "Ziel nicht erreichbar: $Ziel - Pfad korrekt?"
|
|
}
|
|
}
|
|
|
|
# Ein config-Verzeichnis ohne configuration.yaml ist mit hoher Wahrscheinlichkeit
|
|
# der falsche Ordner - lieber hier abbrechen als Dateien irgendwohin streuen.
|
|
if (-not (Test-Path (Join-Path $Ziel "configuration.yaml"))) {
|
|
throw "In $Ziel liegt keine configuration.yaml - ist das wirklich das config-Verzeichnis von HA?"
|
|
}
|
|
Gut "Ziel: $Ziel"
|
|
|
|
# ------------------------------------------------------- Zielordner prüfen
|
|
$zielOrdner = Join-Path $Ziel "custom_components\audi_dashboard"
|
|
$vorhandeneVersion = $null
|
|
if (Test-Path $zielOrdner) {
|
|
$zielManifest = Join-Path $zielOrdner "manifest.json"
|
|
if (-not (Test-Path $zielManifest)) {
|
|
throw "In $zielOrdner liegt keine manifest.json. Das ist nicht unser Ordner - Abbruch, es wird nichts angefasst."
|
|
}
|
|
$vorhandenesManifest = Get-Content $zielManifest -Raw | ConvertFrom-Json
|
|
if ($vorhandenesManifest.domain -ne "audi_dashboard") {
|
|
throw "$zielOrdner gehört zur Integration '$($vorhandenesManifest.domain)', nicht zu 'audi_dashboard' - Abbruch."
|
|
}
|
|
$vorhandeneVersion = $vorhandenesManifest.version
|
|
Info "bereits installiert: Version $vorhandeneVersion"
|
|
}
|
|
|
|
# ----------------------------------------------------------------- Kopieren
|
|
Schritt "Integration kopieren"
|
|
if ($Pruefen) {
|
|
if ($vorhandeneVersion) { Info "würde ersetzen: $zielOrdner (Version $vorhandeneVersion -> $version)" }
|
|
else { Info "würde anlegen: $zielOrdner (Version $version)" }
|
|
Info "würde sonst nichts anfassen - keine configuration.yaml, keine Fahrzeugdaten"
|
|
} else {
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $Ziel "custom_components") | Out-Null
|
|
if (Test-Path $zielOrdner) {
|
|
# Die Prüfung oben hat bestätigt, dass das unser Ordner ist.
|
|
Remove-Item $zielOrdner -Recurse -Force
|
|
}
|
|
Copy-Item $quelle $zielOrdner -Recurse -Force
|
|
# Übersetzte Reste einer früheren Fassung gehören nicht ins Ziel.
|
|
Get-ChildItem $zielOrdner -Recurse -Directory -Filter "__pycache__" |
|
|
ForEach-Object { Remove-Item $_.FullName -Recurse -Force }
|
|
|
|
# Rücklesen: liegt im Ziel wirklich das, was gemeint war?
|
|
$kontrolle = Join-Path $zielOrdner "manifest.json"
|
|
if (-not (Test-Path $kontrolle)) {
|
|
throw "Nach dem Kopieren fehlt $kontrolle - die Installation ist unvollständig."
|
|
}
|
|
$geschrieben = (Get-Content $kontrolle -Raw | ConvertFrom-Json).version
|
|
if ($geschrieben -ne $version) {
|
|
throw "Nach dem Kopieren steht Version '$geschrieben' statt '$version' im Ziel - die Installation ist unvollständig."
|
|
}
|
|
$anzahl = (Get-ChildItem $zielOrdner -Recurse -File).Count
|
|
Gut "custom_components\audi_dashboard (Version $version, $anzahl Dateien)"
|
|
}
|
|
|
|
# --------------------------------------------------------------- Restliste
|
|
if (-not $vorhandeneVersion) {
|
|
[void]$restliste.Add("Home Assistant neu starten: Einstellungen -> System -> Neu starten. (Beim ersten Start ladt HA die Abhaengigkeit pypdf nach - das kann eine Minute dauern.)")
|
|
[void]$restliste.Add("Integration hinzufuegen: Einstellungen -> Geraete & Dienste -> Integration hinzufuegen -> 'Audi Dashboard'. Danach steht 'Mein Audi' in der Seitenleiste.")
|
|
[void]$restliste.Add("In der App: Einstellungen -> Fahrzeug einrichten -> Setup - Sensoren zuordnen. Im Auslieferstand ist KEIN Sensor vorbelegt; zwingend fuer die Fahrterkennung ist der Zuendungs-/ACC-Sensor (binary_sensor, meist vom Teltonika FMM003). Ein Neustart ist dafuer nicht mehr noetig - Aenderungen wirken sofort.")
|
|
} else {
|
|
[void]$restliste.Add("Home Assistant neu starten: Einstellungen -> System -> Neu starten. (Oder kuerzer: Einstellungen -> Geraete & Dienste -> Audi Dashboard -> Neu laden.)")
|
|
}
|
|
|
|
# Bewusst nicht automatisch eingefügt: der recorder-Block ist eine Entscheidung
|
|
# über den Plattenplatz der Instanz (ein Jahr Fahrzeugverlauf sind grob 1-1,5 GB,
|
|
# siehe Kopf von recorder_snippet.yaml), und viele Instanzen haben bereits einen
|
|
# eigenen recorder:-Block mit anderen Einstellungen. Zusammenführen ist Handarbeit.
|
|
[void]$restliste.Add("ZEITKRITISCH - Datenaufbewahrung: den Block aus recorder_snippet.yaml in die configuration.yaml uebernehmen. Home Assistant loescht Sensor-Verlaeufe sonst nach 10 Tagen; was weg ist, laesst sich auch mit 'Daten importieren aus Home Assistant' nicht mehr nachtragen. Je frueher der Block drin ist, desto mehr Vergangenheit bleibt erhalten. VORHER Einstellungen -> System -> Speicher pruefen: ein Jahr Verlauf braucht grob 1-1,5 GB. Bei wenig freiem Platz (SD-Karte, kleine eMMC) mit purge_keep_days: 90 anfangen - Details im Kopf der Datei.")
|
|
[void]$restliste.Add("Optional, sobald die Sensoren zugeordnet sind: Einstellungen -> Einrichten -> 'Daten importieren aus Home Assistant' holt Fahrten, Tankvorgaenge und Spannungswerte aus dem bereits aufgezeichneten HA-Verlauf nach.")
|
|
|
|
Write-Host ""
|
|
if ($Pruefen) {
|
|
Write-Host " Pruefmodus beendet - es wurde nichts geschrieben." -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host " Die Integration ist installiert." -ForegroundColor Green
|
|
}
|
|
Write-Host ""
|
|
Write-Host " Das fehlt noch:" -ForegroundColor White
|
|
$nr = 1
|
|
foreach ($punkt in $restliste) {
|
|
Write-Host " $nr. $punkt"
|
|
$nr++
|
|
}
|
|
Write-Host ""
|
|
Write-Host " Fuer spaetere Updates reicht derselbe Aufruf noch einmal." -ForegroundColor White
|
|
Write-Host " Die Fahrzeugdaten unter audi_dashboard\ bleiben dabei unangetastet."
|
|
Write-Host ""
|