#!/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 Zielgeraet muss # im Team eingetragen sein. Das Ad-hoc-Profil traegt eine feste Liste von # Geraetekennungen, ein unbekanntes Telefon lehnt die fertige .ipa ab. Ein # neues Geraet wird einmalig angemeldet - per Kabel an diesem Mac, dann # erledigt es -allowProvisioningUpdates selbst, oder von Hand auf # developer.apple.com. Bricht der Archivschritt mit "Your team has no devices" # ab, fehlt genau dieser Eintrag; das ist kein Fehler im Projekt. # # Stand des Kontos, am 2026-08-29 am ausgestellten Profil nachgesehen statt # vermutet: bezahlte Mitgliedschaft, Team RMACS9VLS4, Laufzeit ein Jahr. Die # frueher hier vermerkten sieben Tage galten dem kostenlosen Personal Team und # gelten nicht mehr; ebensowenig das "keine Geraeteverwaltung auf # developer.apple.com", das daran hing. # # Der Export ueber -exportArchive ist erprobt und kein offener Punkt mehr: die # ausgelieferten .ipa vom 2026-08-29 und 2026-08-30 sind auf genau diesem Weg # entstanden und wurden ueber die Luft installiert (siehe ios-luftweg.sh). # Scheitert er doch einmal, bleibt der direkte Weg aufs angeschlossene Geraet: # 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 } # Kalender-Berechtigung, aus demselben Grund an derselben Stelle. # # Warum ZWEI Schluessel: seit iOS 17 gibt es einen eigenen Nur-Schreiben-Zugriff # (NSCalendarsWriteOnlyAccessUsageDescription), aeltere Fassungen kennen nur den # alten Sammelschluessel. Der System-Editor (native/KalenderTermin.swift) fragt # auf iOS 17+ von sich aus gar nicht - die Schluessel sind die Absicherung fuer # den Fall, dass EventKit doch einmal fragt. Fehlt der passende, beendet iOS die # App wortlos, statt eine Rueckfrage zu zeigen. echo "==> Kalender-Berechtigung eintragen" KALENDERGRUND="Traegt Werkstatt- und Wechseltermine in deinen Kalender ein." for SCHLUESSEL in NSCalendarsWriteOnlyAccessUsageDescription NSCalendarsUsageDescription; do if ! /usr/libexec/PlistBuddy -c "Set :$SCHLUESSEL $KALENDERGRUND" "$PLIST" 2>/dev/null; then /usr/libexec/PlistBuddy -c "Add :$SCHLUESSEL string $KALENDERGRUND" "$PLIST" fi /usr/libexec/PlistBuddy -c "Print :$SCHLUESSEL" "$PLIST" >/dev/null || { echo "FEHLER: $SCHLUESSEL liess sich nicht setzen." >&2 exit 1 } done # 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"