Files
audi-app/companion-app/scripts/ios-signieren.sh
T
tobias 17dc42e645 App-Symbol: Entwurf 3 bei 54 Prozent
DataMetric oben, das CI-Fahrzeug in der Mitte, 360 in Audi-Rot unten, auf
Nacht-Grund. Ausgewaehlt aus zwoelf Varianten, jede zweimal gezeigt: gross
und bei 60 pt, der Groesse auf dem Homescreen - daran entschied es sich.

Vektorquelle unter native/logo/, erzeugt von scripts/logo-bauen.cjs. Das
Fahrzeug liest das Skript aus ICONS.audi im Panel, damit es nur eine Quelle
fuer diese Form gibt; die Schriften stecken als data-URI in der SVG, sonst
haengt das Ergebnis davon ab, welche Fonts auf dem rasternden Rechner
installiert sind.

Drei Ausfuehrungen fuer iOS 18: hell mit eigenem Grund, dunkel und getoent
ohne - dort legt das System seinen eigenen Grund darunter, ein
mitgeliefertes Schwarz bliebe als Kasten sichtbar. Getoent wertet iOS die
Helligkeit aus, deshalb wird die 360 dort ein helleres Grau statt Rot.

ios-signieren.sh legt die Vorlage ueber die von Capacitor erzeugte - ios/
ist gitignored, ein in Xcode eingesetztes Symbol waere beim naechsten
cap add ios wieder weg.

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

261 lines
12 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
# 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" <<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"