From 0c60a54c4d71df3f6443bf52297abc128807e2f6 Mon Sep 17 00:00:00 2001 From: Baddi86 Date: Fri, 11 Sep 2026 20:23:45 +0200 Subject: [PATCH] build: LehrerApp.McpBridge ins macOS-App-Bundle aufnehmen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-macos-app.sh publiziert und platziert jetzt zusätzlich zur Hauptapp die MCP-Bridge-Binary (Contents/MacOS/LehrerApp.McpBridge), inklusive Universal-Binary-Handling und eigenständiger Ad-hoc-Signierung vor dem Bundle-weiten codesign --deep-Schritt. Publish/Lipo/Kopier-Logik dafür in eine wiederverwendbare place_binary()-Funktion extrahiert. Nur auf Bash-Syntax geprüft (kein macOS in dieser Session verfügbar) - noch nicht auf echtem macOS gebaut/verifiziert, siehe TODO.md 4.5.27. Co-Authored-By: Claude Sonnet 5 --- TODO.md | 21 ++++++++++ build-macos-app.sh | 100 ++++++++++++++++++++++++++++++++------------- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/TODO.md b/TODO.md index 7179088..c1eb2cf 100644 --- a/TODO.md +++ b/TODO.md @@ -2536,6 +2536,27 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`: einer rohen GUID enthält, und dass eine unveränderte Gruppenmitgliedschaft keine erneute Nachfrage auslöst. +- [x] **4.5.27** `build-macos-app.sh` um die Bridge-Binary erweitert (2026-09-11, aus 4.5.25/4.5.26 + zurückgestellt): `LehrerApp.McpBridge` wird jetzt als zweites Self-Contained-Publish pro + Architektur gebaut und als lose Executable neben die Hauptapp nach + `Contents/MacOS/LehrerApp.McpBridge` gelegt (nicht als `CFBundleExecutable` - sie wird nie + von Finder/launchd gestartet, sondern vom KI-Client direkt über ihren vollen Pfad als + Kindprozess). Die bisher einmalige Publish/Universal-Binary/Kopier-Logik ist in eine + `place_binary()`-Funktion extrahiert und wird für Hauptapp und Bridge gleichermaßen + aufgerufen. Bridge wird **zusätzlich einzeln** ad-hoc signiert (Schritt 6/7), bevor der + Bundle-weite `codesign --deep`-Schritt (7/7) läuft — sie liegt lose in `Contents/MacOS` + statt in einem eigenen `.framework`/`.bundle`, daher nicht verlassen auf das (in dem Fall + nicht sicher dokumentierte) Rekursionsverhalten von `--deep`. + - **Nicht gelöst, bewusst zurückgestellt:** Laufzeit-Duplikation (beide Publishes bringen + ihre eigene self-contained .NET-Runtime mit) — siehe Planungsdokument, spätere Optimierung + über einen framework-dependent Bridge-Build + angepasste `runtimeconfig.json`. + - **Nicht verifiziert:** Diese Session lief auf Windows, ohne Zugriff auf `sips`/`iconutil`/ + `lipo`/`codesign` — das Skript wurde nur auf Bash-Syntax geprüft (`bash -n`), nicht auf + einem echten Mac gebaut/ausgeführt. Vor Verlass auf das erzeugte Bundle: auf macOS + `./build-macos-app.sh` laufen lassen und `open build/LehrerApp.app` sowie einen + MCP-Client, der auf `build/LehrerApp.app/Contents/MacOS/LehrerApp.McpBridge` zeigt, gegen + die laufende App testen. + **Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich, dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem bereits bestehenden Anwesenheits-Tracking aus Kapitel 3 (`ParticipationEntry.Attendance`, diff --git a/build-macos-app.sh b/build-macos-app.sh index 972a6e7..ca14d63 100755 --- a/build-macos-app.sh +++ b/build-macos-app.sh @@ -3,13 +3,20 @@ set -euo pipefail # ============================================================ # build-macos-app.sh -# Baut LehrerApp.Desktop als natives macOS .app-Bundle +# Baut LehrerApp.Desktop als natives macOS .app-Bundle, inklusive +# LehrerApp.McpBridge als Companion-Binary im selben Bundle +# (Contents/MacOS/LehrerApp.McpBridge neben Contents/MacOS/LehrerApp.Desktop) - +# siehe TODO.md 4.5.25 zur Architektur-Begründung (Bridge muss im selben +# Bundle liegen, damit sie ihren Pfad relativ zur Hauptapp ermitteln und sich +# selbst in die MCP-Server-Config eines KI-Clients eintragen kann). # ============================================================ # ---- Konfiguration (bei Bedarf anpassen) -------------------- PROJECT_PATH="LehrerApp.Desktop" # Pfad zum .csproj / Projektordner APP_NAME="LehrerApp" # Name des App-Bundles (LehrerApp.app) EXECUTABLE_NAME="LehrerApp.Desktop" # Name der Binary aus dotnet publish (= AssemblyName des Projekts!) +BRIDGE_PROJECT_PATH="LehrerApp.McpBridge" # Pfad zum Bridge-.csproj / Projektordner +BRIDGE_EXECUTABLE_NAME="LehrerApp.McpBridge" # Name der Bridge-Binary (= AssemblyName des Bridge-Projekts!) BUNDLE_ID="de.deinname.lehrerapp" # CFBundleIdentifier VERSION="1.0.0" # CFBundleShortVersionString MIN_MACOS="12.0" # LSMinimumSystemVersion @@ -25,49 +32,67 @@ CONTENTS_DIR="${APP_BUNDLE}/Contents" MACOS_DIR="${CONTENTS_DIR}/MacOS" RESOURCES_DIR="${CONTENTS_DIR}/Resources" -echo "=== 1/6: Alte Build-Artefakte entfernen ===" +echo "=== 1/7: Alte Build-Artefakte entfernen ===" rm -rf "${BUILD_DIR}" mkdir -p "${BUILD_DIR}" -echo "=== 2/6: dotnet publish für jede Architektur ===" +echo "=== 2/7: dotnet publish für jede Architektur (Hauptapp + Bridge) ===" for RID in "${RUNTIME_IDS[@]}"; do - echo " -> Publish für ${RID}" + echo " -> Publish Hauptapp für ${RID}" dotnet publish "${PROJECT_PATH}" \ -c Release \ -r "${RID}" \ --self-contained true \ -p:PublishSingleFile=false \ -o "${BUILD_DIR}/publish/${RID}" + + echo " -> Publish McpBridge für ${RID}" + dotnet publish "${BRIDGE_PROJECT_PATH}" \ + -c Release \ + -r "${RID}" \ + --self-contained true \ + -p:PublishSingleFile=false \ + -o "${BUILD_DIR}/publish-bridge/${RID}" done -echo "=== 3/6: App-Bundle-Struktur anlegen ===" +echo "=== 3/7: App-Bundle-Struktur anlegen (Hauptapp + Bridge) ===" mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" -if [ "${UNIVERSAL_BUILD}" = true ] && [ "${#RUNTIME_IDS[@]}" -ge 2 ]; then - echo " -> Universal Binary via lipo erzeugen" - SRC_A="${BUILD_DIR}/publish/${RUNTIME_IDS[0]}/${EXECUTABLE_NAME}" - SRC_B="${BUILD_DIR}/publish/${RUNTIME_IDS[1]}/${EXECUTABLE_NAME}" +# Kopiert den Publish-Output einer Architektur (oder beider via lipo zu einem Universal Binary) +# nach MACOS_DIR. Für Hauptapp und Bridge wiederverwendet - beide sind self-contained-Publishes +# derselben TFM, gemeinsame .NET-Runtime-Dateien werden beim zweiten Aufruf einfach mit sich +# selbst überschrieben (keine Laufzeit-Duplikate im fertigen Bundle vermieden, nur der +# Publish-Ordner enthält sie doppelt - siehe TODO.md 4.5.25 zur noch offenen +# Runtime-Dedup-Optimierung, für v1 bewusst nicht angegangen). +place_binary() { + local publish_root="$1" + local executable_name="$2" - # Alle Nicht-Binärdateien (Assets etc.) aus der ersten Architektur übernehmen - rsync -a --exclude "${EXECUTABLE_NAME}" "${BUILD_DIR}/publish/${RUNTIME_IDS[0]}/" "${MACOS_DIR}/" + if [ "${UNIVERSAL_BUILD}" = true ] && [ "${#RUNTIME_IDS[@]}" -ge 2 ]; then + local src_a="${publish_root}/${RUNTIME_IDS[0]}/${executable_name}" + local src_b="${publish_root}/${RUNTIME_IDS[1]}/${executable_name}" + rsync -a --exclude "${executable_name}" "${publish_root}/${RUNTIME_IDS[0]}/" "${MACOS_DIR}/" + lipo -create -output "${MACOS_DIR}/${executable_name}" "${src_a}" "${src_b}" + else + cp -r "${publish_root}/${RUNTIME_IDS[0]}/"* "${MACOS_DIR}/" + fi - lipo -create -output "${MACOS_DIR}/${EXECUTABLE_NAME}" "${SRC_A}" "${SRC_B}" -else - echo " -> Einzelne Architektur (${RUNTIME_IDS[0]}) übernehmen" - cp -r "${BUILD_DIR}/publish/${RUNTIME_IDS[0]}/"* "${MACOS_DIR}/" -fi + if [ ! -f "${MACOS_DIR}/${executable_name}" ]; then + echo "FEHLER: Erwartete Executable '${executable_name}' nicht in ${MACOS_DIR} gefunden." + echo "Tatsächlicher Inhalt von ${publish_root}/${RUNTIME_IDS[0]}/:" + ls "${publish_root}/${RUNTIME_IDS[0]}/" + echo "-> Passe den entsprechenden EXECUTABLE_NAME im Skript-Kopf an." + exit 1 + fi + chmod +x "${MACOS_DIR}/${executable_name}" +} -if [ ! -f "${MACOS_DIR}/${EXECUTABLE_NAME}" ]; then - echo "FEHLER: Erwartete Executable '${EXECUTABLE_NAME}' nicht in ${MACOS_DIR} gefunden." - echo "Tatsächlicher Inhalt von ${BUILD_DIR}/publish/${RUNTIME_IDS[0]}/:" - ls "${BUILD_DIR}/publish/${RUNTIME_IDS[0]}/" - echo "-> Passe EXECUTABLE_NAME im Skript-Kopf entsprechend an." - exit 1 -fi +echo " -> Hauptapp" +place_binary "${BUILD_DIR}/publish" "${EXECUTABLE_NAME}" +echo " -> Bridge" +place_binary "${BUILD_DIR}/publish-bridge" "${BRIDGE_EXECUTABLE_NAME}" -chmod +x "${MACOS_DIR}/${EXECUTABLE_NAME}" - -echo "=== 4/6: Icon konvertieren (.icns) ===" +echo "=== 4/7: Icon konvertieren (.icns) ===" if [ -f "${ICON_PNG}" ]; then ICONSET_DIR="${BUILD_DIR}/icon.iconset" mkdir -p "${ICONSET_DIR}" @@ -90,7 +115,7 @@ else ICON_KEY="" fi -echo "=== 5/6: Info.plist schreiben ===" +echo "=== 5/7: Info.plist schreiben ===" cat > "${CONTENTS_DIR}/Info.plist" < @@ -110,7 +135,23 @@ cat > "${CONTENTS_DIR}/Info.plist" < PLIST -echo "=== 6/6: Signieren ===" +# Die Bridge ist bewusst NICHT CFBundleExecutable - sie wird nie von Finder/launchd gestartet, +# sondern ausschließlich vom KI-Client als Kindprozess über ihren vollen Pfad +# (Contents/MacOS/LehrerApp.McpBridge). Kein eigener Eintrag im Info.plist nötig. + +echo "=== 6/7: Bridge-Binary einzeln signieren ===" +if [ "${CODESIGN_ADHOC}" = true ]; then + # Getrennt vom Bundle-weiten --deep-Signieren in Schritt 7: die Bridge ist eine zweite, lose + # liegende Mach-O-Executable direkt in Contents/MacOS (kein eigenes .framework/.bundle) - explizit + # zuerst signieren statt sich allein auf die Rekursion von --deep zu verlassen, damit die + # Bridge auch dann ein gültiges Signat trägt, falls --deep das nicht selbst abdeckt. + codesign --force --sign - "${MACOS_DIR}/${BRIDGE_EXECUTABLE_NAME}" + echo " -> Bridge ad-hoc signiert." +else + echo " -> Signierung übersprungen." +fi + +echo "=== 7/7: Bundle-weit signieren ===" if [ "${CODESIGN_ADHOC}" = true ]; then codesign --force --deep --sign - "${APP_BUNDLE}" echo " -> Ad-hoc signiert (nur lokal lauffähig, Gatekeeper-Warnung bei Weitergabe)." @@ -120,4 +161,5 @@ fi echo "" echo "Fertig! App-Bundle liegt unter: ${APP_BUNDLE}" -echo "Test mit: open '${APP_BUNDLE}'" \ No newline at end of file +echo "Test mit: open '${APP_BUNDLE}'" +echo "Bridge-Binary für die MCP-Server-Config eines KI-Clients: ${APP_BUNDLE}/Contents/MacOS/${BRIDGE_EXECUTABLE_NAME}"