e999d629c5
Test-Path auf einem UNC-Pfad fragt nie nach einem Passwort - ohne eine bereits bestehende SMB-Sitzung liefert es bei einem geschuetzten Share einfach "false", ganz ohne Fehlermeldung. Das Skript verliess sich bisher darauf, dass eine Sitzung schon von Hand aufgebaut wurde (Explorer oder "net use"), und meldete sonst "Ziel nicht erreichbar" - selbst wenn Pfad und Zugangsdaten korrekt waren. Jetzt fragt es bei fehlender Erreichbarkeit selbst nach Benutzername/Kennwort und baut die Sitzung per "net use" auf, bevor es endgueltig abbricht. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
279 lines
14 KiB
PowerShell
279 lines
14 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 }
|
|
|
|
$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." }
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path $Ziel)) {
|
|
# 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.
|
|
# Wer den Pfad vorher nicht schon per Explorer oder "net use" verbunden
|
|
# hatte, bekam deshalb einen irreführenden "nicht erreichbar"-Fehler, auch
|
|
# wenn Pfad und Zugangsdaten korrekt waren. Deshalb hier selbst nachfragen
|
|
# und die Sitzung aufbauen, statt das als manuellen Vorschritt vorauszusetzen.
|
|
if ($Ziel -match '^\\\\') {
|
|
Warnung "Kein Zugriff auf $Ziel - vermutlich fehlt die Anmeldung am Samba-Share."
|
|
Write-Host ""
|
|
$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.
|
|
& net use $Ziel /delete /y *> $null
|
|
|
|
& net use $Ziel /user:$nutzer $kennwort
|
|
$kennwort = $null
|
|
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 ""
|