Files
audi-app/companion-app/scripts/ios-signieren.sh
T
tobias b524c1d488 Xcode-Lauf ohne Handgriffe: Version, Profile, Luftweg
Drei Schritte, die bisher am Entwickler hingen, laufen jetzt im Bau mit:

- Versionsnummer aus der manifest.json ins Info.plist. Capacitors Vorlage
  setzt 1.0/1 und laesst es dabei - zwei nacheinander aufgespielte .ipa waren
  auf dem Geraet nicht zu unterscheiden.

- Zwischengespeicherte Bereitstellungsprofile dieser App wegraeumen.
  -allowProvisioningUpdates zieht ein neues Profil nur, wenn kein passendes
  herumliegt; ein vorhandenes wird stillschweigend weiterbenutzt, auch wenn
  sich die Berechtigungen geaendert haben. Genau daran ist am 2026-08-29 eine
  Installation gescheitert, und mit der neuen App-Gruppe der Share-Erweiterung
  ist derselbe Fall wieder eingetreten. Entfernt wird nur, was zu dieser App
  gehoert; --profile-behalten ueberspringt es.

- ios-luftweg.sh haengt am Ende des Baus. manifest.plist und Icons landen
  damit ohne zweiten Aufruf in auslieferung/, und der Schluss nennt die
  itms-services-Adresse zum Oeffnen in Safari.

Ausserdem die Reihenfolge im Skript geradegezogen: der Kommentar zur
Standort-Berechtigung stand ueber dem Share-Block statt ueber seinem eigenen
Code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 14:30:27 +02:00

242 lines
11 KiB
Bash
Executable File

#!/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=<UDID>' -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
# 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" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key><string>ad-hoc</string>
<key>teamID</key><string>$TEAM</string>
<key>signingStyle</key><string>automatic</string>
<key>stripSwiftSymbols</key><true/>
</dict>
</plist>
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 <UDID> $APP/auslieferung/App.ipa"