This repository was archived by the owner on May 24, 2026. It is now read-only.
Add direct connection support and fix persistent mode (#58) #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Release | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| tags: [ 'v*' ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| jobs: | |
| build-test: | |
| name: Build & Test | |
| runs-on: macos-26 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| if [ -d "/Applications/Xcode_26.2.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.app" | |
| elif [ -d "/Applications/Xcode_26.2.0.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.0.app" | |
| else | |
| XCODE_PATH=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -rV | head -1) | |
| fi | |
| echo "Selected Xcode: $XCODE_PATH" | |
| sudo xcode-select -s "$XCODE_PATH" | |
| xcodebuild -version | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'preview' | |
| - name: Install MAUI workload | |
| run: dotnet workload install maui | |
| - name: Build tests | |
| run: dotnet build PolyPilot.Tests --configuration Release | |
| - name: Run tests | |
| run: | | |
| dotnet test PolyPilot.Tests \ | |
| --configuration Release \ | |
| --no-build \ | |
| --verbosity normal \ | |
| --logger "trx;LogFileName=test-results.trx" | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: test-results | |
| path: '**/TestResults/*.trx' | |
| retention-days: 7 | |
| publish-maccatalyst: | |
| name: Publish Mac Catalyst App | |
| runs-on: macos-26 | |
| needs: build-test | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| run: | | |
| ls -d /Applications/Xcode*.app 2>/dev/null | |
| if [ -d "/Applications/Xcode_26.2.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.app" | |
| elif [ -d "/Applications/Xcode_26.2.0.app" ]; then | |
| XCODE_PATH="/Applications/Xcode_26.2.0.app" | |
| else | |
| XCODE_PATH=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -rV | head -1) | |
| fi | |
| echo "Selected Xcode: $XCODE_PATH" | |
| sudo xcode-select -s "$XCODE_PATH" | |
| sudo xcodebuild -license accept 2>/dev/null || true | |
| xcodebuild -version | |
| echo "MD_APPLE_SDK_ROOT=$XCODE_PATH" >> $GITHUB_ENV | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'preview' | |
| - name: Install MAUI workload | |
| run: dotnet workload install maui | |
| - name: Import signing certificate and provisioning profile | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_PROVISIONING_PROFILE: ${{ secrets.APPLE_PROVISIONING_PROFILE }} | |
| KEYCHAIN_PASSWORD: ${{ github.run_id }} | |
| run: | | |
| CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" | |
| mkdir -p "$PROFILE_DIR" | |
| echo "Installing provisioning profile..." | |
| echo -n "$APPLE_PROVISIONING_PROFILE" | base64 --decode > $RUNNER_TEMP/profile.mobileprovision | |
| PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(security cms -D -i $RUNNER_TEMP/profile.mobileprovision)) | |
| cp $RUNNER_TEMP/profile.mobileprovision "$PROFILE_DIR/$PROFILE_UUID.mobileprovision" | |
| echo "Installed profile with UUID: $PROFILE_UUID" | |
| echo "Importing signing certificate..." | |
| echo -n "$APPLE_CERTIFICATE_P12" | base64 --decode > $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| echo "Certificate and profile setup complete." | |
| - name: Determine version | |
| id: version | |
| run: | | |
| SHA="${GITHUB_SHA::8}" | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VER="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VER="1.0" | |
| fi | |
| echo "app_version=$VER" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "Version: $VER+$SHA" | |
| - name: Publish Mac Catalyst App (Signed) | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} | |
| run: | | |
| dotnet publish PolyPilot/PolyPilot.csproj \ | |
| -f net10.0-maccatalyst \ | |
| -c Release \ | |
| -p:CreatePackage=false \ | |
| -p:EnableCodeSigning=true \ | |
| -p:CodesignKey="$APPLE_CODESIGN_IDENTITY" \ | |
| -p:ApplicationDisplayVersion=${{ steps.version.outputs.app_version }} | |
| mkdir -p ./artifacts/maccatalyst | |
| cp -R PolyPilot/bin/Release/net10.0-maccatalyst/PolyPilot.app ./artifacts/maccatalyst/ | |
| - name: Re-sign with Hardened Runtime | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} | |
| run: | | |
| APP_PATH=$(find ./artifacts/maccatalyst -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Re-signing with hardened runtime: $APP_PATH" | |
| codesign --force --options runtime --timestamp \ | |
| --entitlements PolyPilot/Platforms/MacCatalyst/Entitlements.plist \ | |
| --sign "$APPLE_CODESIGN_IDENTITY" \ | |
| "$APP_PATH" | |
| fi | |
| - name: Publish Mac Catalyst App (Unsigned) | |
| if: env.APPLE_CERTIFICATE_P12 == '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| run: | | |
| dotnet publish PolyPilot/PolyPilot.csproj \ | |
| -f net10.0-maccatalyst \ | |
| -c Release \ | |
| -p:CreatePackage=false \ | |
| -p:EnableCodeSigning=false \ | |
| -p:ApplicationDisplayVersion=${{ steps.version.outputs.app_version }} | |
| mkdir -p ./artifacts/maccatalyst | |
| cp -R PolyPilot/bin/Release/net10.0-maccatalyst/PolyPilot.app ./artifacts/maccatalyst/ | |
| - name: Verify code signature | |
| if: env.APPLE_CERTIFICATE_P12 != '' | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| run: | | |
| APP_PATH=$(find ./artifacts/maccatalyst -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Verifying signature for: $APP_PATH" | |
| codesign -dvv "$APP_PATH" 2>&1 | tee /tmp/codesign-output.txt | |
| if grep -q "flags=0x10000(runtime)" /tmp/codesign-output.txt; then | |
| echo "✅ Hardened Runtime is enabled" | |
| else | |
| echo "::error::Hardened Runtime is NOT enabled — notarization will fail" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Create ZIP archive | |
| run: | | |
| APP_PATH=$(find ./artifacts/maccatalyst -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/PolyPilot.zip | |
| echo "Created archive: ./artifacts/PolyPilot.zip" | |
| ls -lh ./artifacts/PolyPilot.zip | |
| else | |
| echo "No .app bundle found, creating archive of all artifacts" | |
| cd ./artifacts/maccatalyst && zip -r ../PolyPilot.zip . | |
| fi | |
| - name: Upload Mac Catalyst App | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: PolyPilot.app | |
| path: ./artifacts/PolyPilot.zip | |
| retention-days: 30 | |
| - name: Set commit status with artifact link | |
| if: always() | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -f "./artifacts/PolyPilot.zip" ]; then | |
| STATE="success" | |
| DESC="PolyPilot.app built successfully" | |
| else | |
| STATE="failure" | |
| DESC="PolyPilot.app build failed" | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.sha }} \ | |
| -f state="$STATE" \ | |
| -f target_url="$ARTIFACT_URL" \ | |
| -f description="$DESC" \ | |
| -f context="PolyPilot / Download App" | |
| - name: Cleanup keychain | |
| if: always() | |
| run: | | |
| if [ -f "$RUNNER_TEMP/app-signing.keychain-db" ]; then | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db | |
| fi | |
| notarize-maccatalyst: | |
| name: Notarize Mac Catalyst App | |
| runs-on: macos-26 | |
| needs: publish-maccatalyst | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - name: Download Mac Catalyst artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: PolyPilot.app | |
| path: ./artifacts | |
| - name: Notarize with retry | |
| env: | |
| APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }} | |
| APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | |
| APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }} | |
| run: | | |
| mkdir -p ./artifacts/maccatalyst | |
| ditto -x -k ./artifacts/PolyPilot.zip ./artifacts/maccatalyst | |
| APP_PATH=$(find ./artifacts/maccatalyst -name "*.app" -type d | head -1) | |
| if [ -z "$APP_PATH" ]; then | |
| echo "::error::No .app bundle found in artifact" | |
| exit 1 | |
| fi | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/notarize.zip | |
| MAX_RETRIES=3 | |
| RETRY_DELAY=30 | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "Notarization attempt $i of $MAX_RETRIES..." | |
| if xcrun notarytool submit ./artifacts/notarize.zip \ | |
| --apple-id "$APPLE_NOTARIZATION_APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_NOTARIZATION_TEAM_ID" \ | |
| --wait; then | |
| echo "Notarization succeeded on attempt $i" | |
| break | |
| fi | |
| if [ $i -lt $MAX_RETRIES ]; then | |
| echo "Attempt $i failed, retrying in ${RETRY_DELAY}s..." | |
| sleep $RETRY_DELAY | |
| RETRY_DELAY=$((RETRY_DELAY * 2)) | |
| else | |
| echo "::error::Notarization failed after $MAX_RETRIES attempts" | |
| exit 1 | |
| fi | |
| done | |
| rm ./artifacts/notarize.zip | |
| xcrun stapler staple "$APP_PATH" | |
| rm ./artifacts/PolyPilot.zip | |
| ditto -c -k --keepParent "$APP_PATH" ./artifacts/PolyPilot.zip | |
| echo "Created notarized archive" | |
| ls -lh ./artifacts/PolyPilot.zip | |
| - name: Upload notarized Mac Catalyst App | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: PolyPilot.app | |
| path: ./artifacts/PolyPilot.zip | |
| overwrite: true | |
| retention-days: 30 | |
| create-release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [notarize-maccatalyst] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download Mac Catalyst artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: PolyPilot.app | |
| path: ./release | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| ./release/PolyPilot.zip | |
| generate_release_notes: false | |
| draft: false | |
| prerelease: ${{ contains(github.ref, '-') }} |