#!/usr/bin/env bash # Baut die native iOS-Huelle und signiert sie zu einer installierbaren .ipa. # Nur auf einem Mac mit Xcode lauffaehig. Siehe ../../UMSETZUNGSPLAN.md Phase 10. # # Warum es dieses Skript gibt: `ios/` ist absichtlich gitignored (aus dem # Webbuendel jederzeit wiederherstellbar). Damit verschwindet aber auch jede # Signatureinstellung, die man in Xcode von Hand setzt, beim naechsten # `npx cap add ios` wieder. Die Team-Kennung gehoert deshalb hierher - in eine # versionierte Datei - und wird dem Build von aussen mitgegeben. # # Voraussetzung, die dieses Skript nicht herstellen kann: das iPhone muss # per Kabel an diesem Mac haengen und vertraut sein. Apple erzeugt ein # Development-Profil nur fuer konkrete Geraete, und dieses Konto ist ein # kostenloses Personal Team - da gibt es keine Geraeteverwaltung auf # developer.apple.com, das Anmelden der Geraetekennung macht Xcode selbst ueber # -allowProvisioningUpdates am angeschlossenen Telefon. Ohne Telefon bricht der # Archivschritt mit "Your team has no devices" ab; das ist kein Fehler im # Projekt. # # Ebenfalls Folge des kostenlosen Kontos: Profil, App-ID und Geraeteeintrag # verfallen nach 7 Tagen, die App muss dann neu aufgespielt werden. # # Ungeprueft, weil bisher kein Geraet zur Verfuegung stand: ob der # -exportArchive-Schritt mit einem Personal Team ueberhaupt eine brauchbare # .ipa liefert. Kostenloses Signieren ist auf "direkt aufs angeschlossene # Geraet starten" ausgelegt, nicht auf ein weitergebbares Archiv. Scheitert der # Export, ist der direkte Weg der richtige, nicht die Fehlersuche am Export: # xcodebuild -project ios/App/App.xcodeproj -scheme App \ # -destination 'id=' -allowProvisioningUpdates \ # DEVELOPMENT_TEAM=$TEAM install set -euo pipefail HIER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP="$(dirname "$HIER")" # Dieselbe Kennung wie zu Zeiten des kostenlosen Kontos: die bezahlte # Mitgliedschaft hat das bestehende Team hochgestuft, statt ein neues # anzulegen. Zwischenzeitlich stand hier das Gegenteil - widerlegt am # 2026-08-29 durch das ausgestellte Profil (Team RMACS9VLS4, Laufzeit ein # Jahr statt sieben Tage). Ueberschreibbar ueber APPLE_TEAM_ID. TEAM="${APPLE_TEAM_ID:-RMACS9VLS4}" AUSGABE="$APP/ios/build" ARCHIV="$AUSGABE/DataMetric360.xcarchive" cd "$APP" # Ohne diese Pruefung baut/signiert das Skript zuverlaessig - aber nur den # Stand, der gerade lokal ausgecheckt ist. Ein vergessenes `git pull` waere # damit ein stiller Veraltungsfehler wie schon mehrfach an anderer Stelle in # diesem Projekt (Cache-Busting, OTA-Buendel-Abgleich, VERSIONIERUNG.md) - hier # stattdessen ein lauter Abbruch statt eines optisch/inhaltlich alten Builds. echo "==> Git-Stand pruefen" if [ -n "$(git status --porcelain)" ]; then echo "FEHLER: Uncommittete Aenderungen im Arbeitsverzeichnis." >&2 git status --short >&2 echo "Erst committen oder stashen, dann erneut versuchen." >&2 exit 1 fi git fetch origin LOKAL="$(git rev-parse HEAD)" FERN="$(git rev-parse origin/main)" if [ "$LOKAL" != "$FERN" ]; then echo "FEHLER: Lokaler Stand ($LOKAL) weicht von origin/main ($FERN) ab." >&2 echo "Erst 'git pull' ausfuehren - sonst signiert dieses Skript einen veralteten Stand." >&2 exit 1 fi echo " HEAD == origin/main ($LOKAL), aktuell." echo "==> Webbuendel bauen" npm run build echo "==> Native Huelle synchronisieren" if [ ! -d ios ]; then echo "==> ios/ fehlt, wird neu erzeugt" npx cap add ios fi npx cap sync ios # Standort-Berechtigung ins Info.plist eintragen. # # Ohne diesen Schluessel zeigt iOS beim Aufruf von navigator.geolocation in # der WebView (src/screens/Standort.tsx) ueberhaupt keine Rueckfrage: der # Aufruf scheitert sofort, die App meldet "GPS-Freigabe fehlt", und unter # Einstellungen > DataMetric360 > Ort fehlt die Auswahl "Beim Verwenden der # App" - es bleiben nur "Nie" und "Beim Teilen". Nachgewiesen am 2026-08-31 # an der ausgelieferten App.ipa: kein einziger Location-Schluessel darin. # # Warum hier und nicht in Xcode: ios/ ist gitignored und wird von # `npx cap add ios` aus Capacitors Vorlage neu erzeugt - die kennt keine # Standort-Schluessel. Ein Eintrag von Hand in Xcode waere beim naechsten # Erzeugen wieder verschwunden, genau wie es der Team-Kennung oben ergangen # waere. Deshalb steht er versioniert hier und wird nach jedem `cap sync` # neu gesetzt. # # Nur "WhenInUse": berechnet wird der Abstand zum geparkten Fahrzeug, waehrend # jemand die Standort-Seite ansieht. Hintergrundortung braucht die App nicht, # und was sie nicht braucht, soll sie auch nicht erfragen. echo "==> Standort-Berechtigung eintragen" PLIST="ios/App/App/Info.plist" GRUND="Zeigt auf der Standort-Karte, wie weit du vom geparkten Fahrzeug entfernt bist." if ! /usr/libexec/PlistBuddy -c "Set :NSLocationWhenInUseUsageDescription $GRUND" "$PLIST" 2>/dev/null; then /usr/libexec/PlistBuddy -c "Add :NSLocationWhenInUseUsageDescription string $GRUND" "$PLIST" fi /usr/libexec/PlistBuddy -c "Print :NSLocationWhenInUseUsageDescription" "$PLIST" >/dev/null || { echo "FEHLER: NSLocationWhenInUseUsageDescription liess sich nicht setzen." >&2 exit 1 } # Share-Erweiterung ins Xcode-Projekt haengen. # # Aus demselben Grund wie die Team-Kennung und der Standort-Schluessel oben: # ios/ ist gitignored, also verschwindet jedes von Hand angelegte Ziel beim # naechsten `npx cap add ios`. Die Quellen liegen versioniert unter native/, # das Skript spielt sie bei jedem Bau wieder ein. Es ist idempotent - ein # zweiter Lauf ueber ein fertiges Projekt aendert nichts. # # Schlaegt es fehl, bricht der ganze Bau ab: eine App ohne den Eintrag im # Teilen-Blatt sieht fertig aus und ist es nicht, und der Unterschied faellt # erst auf dem Geraet auf. echo "==> Share-Erweiterung einrichten" node scripts/ios-teilen-einrichten.mjs # App-Symbol einspielen. # # Capacitors Vorlage bringt ein eigenes Platzhalter-Symbol mit, und ios/ ist # gitignored - ein in Xcode eingesetztes Symbol waere beim naechsten # `npx cap add ios` wieder weg. Die Vorlage liegt deshalb versioniert unter # native/AppIcon.appiconset und wird hier ueber die von Capacitor erzeugte # gelegt. # # Drei Ausfuehrungen, wie iOS 18 sie erwartet: hell mit eigenem Grund, dunkel # und getoent ohne - dort legt das System seinen eigenen Grund darunter, und # ein mitgeliefertes Schwarz bliebe darauf als Kasten sichtbar. echo "==> App-Symbol einspielen" SYMBOLZIEL="ios/App/App/Assets.xcassets/AppIcon.appiconset" rm -rf "$SYMBOLZIEL" mkdir -p "$SYMBOLZIEL" cp -R native/AppIcon.appiconset/. "$SYMBOLZIEL"/ [ -f "$SYMBOLZIEL/Contents.json" ] || { echo "FEHLER: App-Symbol nicht eingespielt." >&2; exit 1; } echo " hell, dunkel, getoent" # Versionsnummer aus der manifest.json in die App schreiben. # # Capacitors Vorlage setzt 1.0/1 und laesst es dabei - zwei nacheinander # aufgespielte .ipa sind auf dem Geraet dadurch nicht zu unterscheiden, und # man weiss nicht, welche gerade drauf ist. Dieselbe Zahl, die schon Panel, # OTA-Buendel und __APP_VERSION__ tragen (siehe VERSIONIERUNG.md): eine # Quelle, kein zweiter Ort zum Vergessen. echo "==> Versionsnummer eintragen" VERSION="$(node -p "require('../custom_components/audi_dashboard/manifest.json').version")" [ -n "$VERSION" ] || { echo "FEHLER: keine Version in der manifest.json." >&2; exit 1; } if ! /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$PLIST" 2>/dev/null; then /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $VERSION" "$PLIST" fi echo " $VERSION" # Zwischengespeicherte Bereitstellungsprofile dieser App wegraeumen. # # `-allowProvisioningUpdates` zieht ein neues Profil nur, wenn kein passendes # herumliegt - ein vorhandenes wird stillschweigend weiterbenutzt, auch wenn # sich seither die Berechtigungen geaendert haben. Genau daran ist am # 2026-08-29 eine Installation gescheitert ("Integritaet konnte nicht # geprueft werden"): das eingebettete Profil kannte ein Geraet, das Team # hatte inzwischen zwei. Mit der App-Gruppe der Share-Erweiterung ist # derselbe Fall erneut eingetreten - jedes vor dem 2026-08-31 gezogene # Profil kennt sie nicht. # # Weggeraeumt wird nur, was zu dieser App gehoert (Bundle-ID im entpackten # Profil gesucht), nicht der ganze Ordner - dort liegen auch die Profile # anderer Projekte. Mit --profile-behalten laesst sich der Schritt # ueberspringen. if [ "${1:-}" != "--profile-behalten" ]; then echo "==> Alte Bereitstellungsprofile wegraeumen" PROFILE="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles" if [ -d "$PROFILE" ]; then ANZAHL=0 for datei in "$PROFILE"/*.mobileprovision; do [ -e "$datei" ] || continue if security cms -D -i "$datei" 2>/dev/null | grep -qF "app.datametric360"; then rm -f "$datei" ANZAHL=$((ANZAHL + 1)) fi done echo " $ANZAHL entfernt - xcodebuild zieht sie gleich neu" else echo " keine vorhanden" fi else echo "==> Bereitstellungsprofile bleiben (--profile-behalten)" fi echo "==> Archiv bauen und signieren (Team $TEAM)" rm -rf "$AUSGABE" mkdir -p "$AUSGABE" xcodebuild \ -project ios/App/App.xcodeproj \ -scheme App \ -configuration Release \ -destination 'generic/platform=iOS' \ -archivePath "$ARCHIV" \ -allowProvisioningUpdates \ DEVELOPMENT_TEAM="$TEAM" \ archive # Ad-hoc, nicht development: das ist die von Apple vorgesehene Art fuer die # Installation ueber die Luft (siehe ios-luftweg.sh) und deckt den Weg per # Kabel gleich mit ab. Sie verlangt ein Apple-Distribution-Zertifikat, das # -allowProvisioningUpdates bei Bedarf selbst anlegt - moeglich erst seit der # bezahlten Mitgliedschaft. Die Signatur laeuft damit ein Jahr. cat > "$AUSGABE/ExportOptions.plist" < methodad-hoc teamID$TEAM signingStyleautomatic stripSwiftSymbols PLIST echo "==> .ipa exportieren" xcodebuild -exportArchive \ -archivePath "$ARCHIV" \ -exportOptionsPlist "$AUSGABE/ExportOptions.plist" \ -exportPath "$AUSGABE" \ -allowProvisioningUpdates # Die fertige .ipa liegt versioniert unter auslieferung/, weil ios/ gitignored # ist und das Ergebnis sonst nur auf diesem einen Mac existierte. mkdir -p "$APP/auslieferung" cp "$AUSGABE/App.ipa" "$APP/auslieferung/App.ipa" # Auslieferung ueber die Luft gleich mitschreiben. # # manifest.plist und die Installationsseite hingen bisher an einem zweiten, # von Hand aufzurufenden Skript - und ohne sie ist die frische .ipa auf dem # Telefon nicht installierbar, obwohl sie fertig im Repo liegt. Ein Schritt, # den man vergessen kann, gehoert in den Bau. # # Die Basis-Adresse ist die des Gitea-Repos: es liefert die Dateien bereits # ueber HTTPS mit oeffentlich vertrauenswuerdigem Zertifikat aus, was iOS # fuer diesen Weg zwingend verlangt (siehe ios-luftweg.sh). Ueberschreibbar # ueber LUFTWEG_BASIS, falls der Weg einmal ueber einen eigenen Host geht. echo "==> Auslieferung ueber die Luft vorbereiten" LUFTWEG_BASIS="${LUFTWEG_BASIS:-https://gitea.nothaft.cloud/paul/audi-app/raw/branch/main/companion-app/auslieferung}" bash "$HIER/ios-luftweg.sh" "$LUFTWEG_BASIS" auslieferung echo echo "Fertig: $APP/auslieferung/App.ipa" echo "Version: $VERSION" echo echo "Noch zu tun:" echo " 1. git add companion-app/auslieferung && git commit && git push" echo " (die .ipa und manifest.plist muessen im Repo liegen, sonst kann" echo " das Telefon sie nicht laden)" echo " 2. Auf dem iPhone in Safari oeffnen:" echo " itms-services://?action=download-manifest&url=$LUFTWEG_BASIS/manifest.plist" echo echo "Per Kabel stattdessen: xcrun devicectl device install app --device $APP/auslieferung/App.ipa"