diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index 1b854af..e77849d 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -24,12 +24,61 @@ jobs: - name: Create xcconfig files run: | - cd MacOS/ProxyBridge - cp proxybridge-app.xcconfig Signing-Config-app.xcconfig - cp proxybridge-ext.xcconfig Signing-Config-ext.xcconfig - sed -i '' 's/DEVELOPMENT_TEAM = L.*/DEVELOPMENT_TEAM = /' Signing-Config-app.xcconfig - sed -i '' 's/DEVELOPMENT_TEAM = L.*/DEVELOPMENT_TEAM = /' Signing-Config-ext.xcconfig - + echo "${{ secrets.MACOS_APP_XCCONFIG }}" > MacOS/ProxyBridge/Signing-Config-app.xcconfig + echo "${{ secrets.MACOS_EXT_XCCONFIG }}" > MacOS/ProxyBridge/Signing-Config-ext.xcconfig + + - name: Install Provisioning Profiles + run: | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + + echo "${{ secrets.MACOS_APP_PROVISION_PROFILE }}" | base64 --decode > /tmp/app.provisionprofile + echo "${{ secrets.MACOS_EXT_PROVISION_PROFILE }}" | base64 --decode > /tmp/ext.provisionprofile + + APP_UUID=$(security cms -D -i /tmp/app.provisionprofile | plutil -extract UUID raw -) + EXT_UUID=$(security cms -D -i /tmp/ext.provisionprofile | plutil -extract UUID raw -) + + cp /tmp/app.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/${APP_UUID}.provisionprofile + cp /tmp/ext.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/${EXT_UUID}.provisionprofile + + echo "Installed app profile: ${APP_UUID}" + echo "Installed ext profile: ${EXT_UUID}" + + - name: Import Certificate + run: | + # Decode certificate + echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > /tmp/certificate.p12 + + # Create a temporary keychain + security create-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + security set-keychain-settings build.keychain + + # Import Developer ID Application certificate + security import /tmp/certificate.p12 \ + -k build.keychain \ + -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" \ + -T /usr/bin/codesign \ + -A + + # Import Developer ID Installer certificate + echo "${{ secrets.MACOS_INSTALLER_CERTIFICATE }}" | base64 --decode > /tmp/installer.p12 + security import /tmp/installer.p12 \ + -k build.keychain \ + -P "${{ secrets.MACOS_INSTALLER_CERTIFICATE_PASSWORD }}" \ + -T /usr/bin/codesign \ + -T /usr/bin/productsign \ + -A + + # Allow codesign and productsign to access the keychain without prompting + security set-key-partition-list -S apple-tool:,apple: -s -k "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + + # Add build.keychain to the keychain search list so productsign can find the cert + security list-keychains -d user -s build.keychain login.keychain-db + + # Verify both certificates are available + security find-identity -v -p codesigning build.keychain + - name: Build Universal Binary run: | cd MacOS/ProxyBridge @@ -40,14 +89,141 @@ jobs: -derivedDataPath build/DerivedData \ ARCHS="arm64 x86_64" \ ONLY_ACTIVE_ARCH=NO \ - CODE_SIGN_IDENTITY="-" \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGNING_ALLOWED=NO \ + OTHER_CODE_SIGN_FLAGS="--keychain build.keychain --timestamp" \ clean build - + - name: Verify Build run: | cd MacOS/ProxyBridge ls -la build/DerivedData/Build/Products/Release/ file build/DerivedData/Build/Products/Release/ProxyBridge.app/Contents/MacOS/ProxyBridge - lipo -archs build/DerivedData/Build/Products/Release/ProxyBridge.app/Contents/MacOS/ProxyBridge \ No newline at end of file + lipo -archs build/DerivedData/Build/Products/Release/ProxyBridge.app/Contents/MacOS/ProxyBridge + # Verify code signature + codesign -dv --verbose=4 build/DerivedData/Build/Products/Release/ProxyBridge.app + + - name: Notarize App + run: | + APP_PATH="MacOS/ProxyBridge/build/DerivedData/Build/Products/Release/ProxyBridge.app" + + # Zip the app for submission + ditto -c -k --keepParent "$APP_PATH" /tmp/ProxyBridge.zip + + # Submit for notarization and wait for result, capture submission ID + SUBMIT_OUTPUT=$(xcrun notarytool submit /tmp/ProxyBridge.zip \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" \ + --wait 2>&1) + + echo "$SUBMIT_OUTPUT" + + # Extract submission ID + SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "^ id:" | head -1 | awk '{print $2}') + echo "Submission ID: $SUBMISSION_ID" + + # Always fetch the detailed log from Apple + if [ -n "$SUBMISSION_ID" ]; then + echo "--- Notarization Log ---" + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || true + fi + + # Fail if notarization was not accepted + if ! echo "$SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "Notarization failed!" + exit 1 + fi + + # Staple the notarization ticket to the app + xcrun stapler staple "$APP_PATH" + + # Verify notarization + spctl -a -vvv -t install "$APP_PATH" + + - name: Build PKG Installer + env: + PKG_ENV_CONTENT: ${{ secrets.PKG_ENV }} + run: | + # Write PKG_ENV to .env but strip SIGNING_IDENTITY so build.sh skips signing + # (productsign inside build.sh cannot access build.keychain — we do it below) + printf '%s' "$PKG_ENV_CONTENT" | grep -v "^SIGNING_IDENTITY" > MacOS/ProxyBridge/.env + + # Copy notarized .app into output/ where build.sh expects it + mkdir -p MacOS/ProxyBridge/output + cp -R MacOS/ProxyBridge/build/DerivedData/Build/Products/Release/ProxyBridge.app \ + MacOS/ProxyBridge/output/ProxyBridge.app + + # Run build script — creates the unsigned .pkg only + cd MacOS/ProxyBridge + chmod +x build.sh + bash build.sh + + PKG_VERSION="3.2.0" + PKG_UNSIGNED="output/ProxyBridge-v${PKG_VERSION}-Universal-Installer.pkg" + PKG_SIGNED="output/ProxyBridge-v${PKG_VERSION}-Universal-Installer-signed.pkg" + + # Sign pkg with explicit keychain — no prompting + echo "Signing installer..." + KEYCHAIN_PATH=$(security list-keychains -d user | grep build | tr -d ' "') + echo "Using keychain: $KEYCHAIN_PATH" + security unlock-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH" + productsign \ + --sign "${{ secrets.MACOS_SIGNING_IDENTITY }}" \ + --keychain "$KEYCHAIN_PATH" \ + "$PKG_UNSIGNED" "$PKG_SIGNED" + mv "$PKG_SIGNED" "$PKG_UNSIGNED" + + # Notarize pkg + echo "Notarizing installer..." + PKG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$PKG_UNSIGNED" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" \ + --wait 2>&1) + + echo "$PKG_SUBMIT_OUTPUT" + + PKG_SUBMISSION_ID=$(echo "$PKG_SUBMIT_OUTPUT" | grep "^ id:" | head -1 | awk '{print $2}') + if [ -n "$PKG_SUBMISSION_ID" ]; then + echo "--- PKG Notarization Log ---" + xcrun notarytool log "$PKG_SUBMISSION_ID" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || true + fi + + if ! echo "$PKG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "PKG notarization failed!" + exit 1 + fi + + xcrun stapler staple "$PKG_UNSIGNED" + echo "PKG signed, notarized and stapled successfully" + + - name: Upload PKG Artifact + uses: actions/upload-artifact@v4 + with: + name: ProxyBridge-macOS-Installer + path: MacOS/ProxyBridge/output/ProxyBridge-*.pkg + retention-days: 30 + + - name: Cleanup + if: always() + run: | + # Delete temporary keychain + security delete-keychain build.keychain || true + + # Remove provisioning profiles + rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*.provisionprofile || true + + # Remove temp cert and profile files + rm -f /tmp/certificate.p12 /tmp/installer.p12 /tmp/app.provisionprofile /tmp/ext.provisionprofile /tmp/ProxyBridge.zip || true + + # Remove .env with sensitive data + rm -f MacOS/ProxyBridge/.env || true + + # Remove xcconfig files with sensitive data + rm -f MacOS/ProxyBridge/Signing-Config-app.xcconfig || true + rm -f MacOS/ProxyBridge/Signing-Config-ext.xcconfig || true \ No newline at end of file diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 4e2f9b6..8fe17c6 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -5,6 +5,9 @@ on: types: [published, created] workflow_dispatch: +permissions: + contents: write + jobs: build-and-release: runs-on: ubuntu-latest diff --git a/.github/workflows/release-mac.yml b/.github/workflows/release-mac.yml new file mode 100644 index 0000000..24e77f5 --- /dev/null +++ b/.github/workflows/release-mac.yml @@ -0,0 +1,233 @@ +name: Release ProxyBridge macOS +permissions: + contents: write + +on: + release: + types: [published, created] + workflow_dispatch: + +jobs: + build-and-release: + runs-on: macos-latest + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.actor == github.repository_owner) + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Create xcconfig files + run: | + echo "${{ secrets.MACOS_APP_XCCONFIG }}" > MacOS/ProxyBridge/Signing-Config-app.xcconfig + echo "${{ secrets.MACOS_EXT_XCCONFIG }}" > MacOS/ProxyBridge/Signing-Config-ext.xcconfig + + - name: Install Provisioning Profiles + run: | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + + echo "${{ secrets.MACOS_APP_PROVISION_PROFILE }}" | base64 --decode > /tmp/app.provisionprofile + echo "${{ secrets.MACOS_EXT_PROVISION_PROFILE }}" | base64 --decode > /tmp/ext.provisionprofile + + APP_UUID=$(security cms -D -i /tmp/app.provisionprofile | plutil -extract UUID raw -) + EXT_UUID=$(security cms -D -i /tmp/ext.provisionprofile | plutil -extract UUID raw -) + + cp /tmp/app.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/${APP_UUID}.provisionprofile + cp /tmp/ext.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/${EXT_UUID}.provisionprofile + + echo "Installed app profile: ${APP_UUID}" + echo "Installed ext profile: ${EXT_UUID}" + + - name: Import Certificate + run: | + # Decode certificate + echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > /tmp/certificate.p12 + + # Create a temporary keychain + security create-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + security set-keychain-settings build.keychain + + # Import Developer ID Application certificate + security import /tmp/certificate.p12 \ + -k build.keychain \ + -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" \ + -T /usr/bin/codesign \ + -A + + # Import Developer ID Installer certificate + echo "${{ secrets.MACOS_INSTALLER_CERTIFICATE }}" | base64 --decode > /tmp/installer.p12 + security import /tmp/installer.p12 \ + -k build.keychain \ + -P "${{ secrets.MACOS_INSTALLER_CERTIFICATE_PASSWORD }}" \ + -T /usr/bin/codesign \ + -T /usr/bin/productsign \ + -A + + # Allow codesign and productsign to access the keychain without prompting + security set-key-partition-list -S apple-tool:,apple: -s -k "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" build.keychain + + # Add build.keychain to the keychain search list so productsign can find the cert + security list-keychains -d user -s build.keychain login.keychain-db + + # Verify both certificates are available + security find-identity -v -p codesigning build.keychain + + - name: Build Universal Binary + run: | + cd MacOS/ProxyBridge + xcodebuild \ + -project ProxyBridge.xcodeproj \ + -scheme ProxyBridge \ + -configuration Release \ + -derivedDataPath build/DerivedData \ + ARCHS="arm64 x86_64" \ + ONLY_ACTIVE_ARCH=NO \ + OTHER_CODE_SIGN_FLAGS="--keychain build.keychain --timestamp" \ + clean build + + - name: Verify Build + run: | + cd MacOS/ProxyBridge + ls -la build/DerivedData/Build/Products/Release/ + file build/DerivedData/Build/Products/Release/ProxyBridge.app/Contents/MacOS/ProxyBridge + lipo -archs build/DerivedData/Build/Products/Release/ProxyBridge.app/Contents/MacOS/ProxyBridge + # Verify code signature + codesign -dv --verbose=4 build/DerivedData/Build/Products/Release/ProxyBridge.app + + - name: Notarize App + run: | + APP_PATH="MacOS/ProxyBridge/build/DerivedData/Build/Products/Release/ProxyBridge.app" + + # Zip the app for submission + ditto -c -k --keepParent "$APP_PATH" /tmp/ProxyBridge.zip + + # Submit for notarization and wait for result, capture submission ID + SUBMIT_OUTPUT=$(xcrun notarytool submit /tmp/ProxyBridge.zip \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" \ + --wait 2>&1) + + echo "$SUBMIT_OUTPUT" + + # Extract submission ID + SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "^ id:" | head -1 | awk '{print $2}') + echo "Submission ID: $SUBMISSION_ID" + + # Always fetch the detailed log from Apple + if [ -n "$SUBMISSION_ID" ]; then + echo "--- Notarization Log ---" + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || true + fi + + # Fail if notarization was not accepted + if ! echo "$SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "Notarization failed!" + exit 1 + fi + + # Staple the notarization ticket to the app + xcrun stapler staple "$APP_PATH" + + # Verify notarization + spctl -a -vvv -t install "$APP_PATH" + + - name: Build PKG Installer + env: + PKG_ENV_CONTENT: ${{ secrets.PKG_ENV }} + run: | + # Write PKG_ENV to .env but strip SIGNING_IDENTITY so build.sh skips signing + # (productsign inside build.sh cannot access build.keychain — we do it below) + printf '%s' "$PKG_ENV_CONTENT" | grep -v "^SIGNING_IDENTITY" > MacOS/ProxyBridge/.env + + # Copy notarized .app into output/ where build.sh expects it + mkdir -p MacOS/ProxyBridge/output + cp -R MacOS/ProxyBridge/build/DerivedData/Build/Products/Release/ProxyBridge.app \ + MacOS/ProxyBridge/output/ProxyBridge.app + + # Run build script — creates the unsigned .pkg only + cd MacOS/ProxyBridge + chmod +x build.sh + bash build.sh + + PKG_VERSION="3.2.0" + PKG_UNSIGNED="output/ProxyBridge-v${PKG_VERSION}-Universal-Installer.pkg" + PKG_SIGNED="output/ProxyBridge-v${PKG_VERSION}-Universal-Installer-signed.pkg" + + # Sign pkg with explicit keychain — no prompting + echo "Signing installer..." + KEYCHAIN_PATH=$(security list-keychains -d user | grep build | tr -d ' "') + echo "Using keychain: $KEYCHAIN_PATH" + security unlock-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH" + productsign \ + --sign "${{ secrets.MACOS_SIGNING_IDENTITY }}" \ + --keychain "$KEYCHAIN_PATH" \ + "$PKG_UNSIGNED" "$PKG_SIGNED" + mv "$PKG_SIGNED" "$PKG_UNSIGNED" + + # Notarize pkg + echo "Notarizing installer..." + PKG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$PKG_UNSIGNED" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" \ + --wait 2>&1) + + echo "$PKG_SUBMIT_OUTPUT" + + PKG_SUBMISSION_ID=$(echo "$PKG_SUBMIT_OUTPUT" | grep "^ id:" | head -1 | awk '{print $2}') + if [ -n "$PKG_SUBMISSION_ID" ]; then + echo "--- PKG Notarization Log ---" + xcrun notarytool log "$PKG_SUBMISSION_ID" \ + --apple-id "${{ secrets.APPLE_ID }}" \ + --password "${{ secrets.APPLE_APP_PASSWORD }}" \ + --team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || true + fi + + if ! echo "$PKG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "PKG notarization failed!" + exit 1 + fi + + xcrun stapler staple "$PKG_UNSIGNED" + echo "PKG signed, notarized and stapled successfully" + + - name: Upload PKG to Release + uses: softprops/action-gh-release@v1 + with: + files: MacOS/ProxyBridge/output/ProxyBridge-*.pkg + + - name: Upload PKG Artifact + uses: actions/upload-artifact@v4 + with: + name: ProxyBridge-macOS-Installer + path: MacOS/ProxyBridge/output/ProxyBridge-*.pkg + retention-days: 30 + + - name: Cleanup + if: always() + run: | + # Delete temporary keychain + security delete-keychain build.keychain || true + + # Remove provisioning profiles + rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*.provisionprofile || true + + # Remove temp cert and profile files + rm -f /tmp/certificate.p12 /tmp/installer.p12 /tmp/app.provisionprofile /tmp/ext.provisionprofile /tmp/ProxyBridge.zip || true + + # Remove .env with sensitive data + rm -f MacOS/ProxyBridge/.env || true + + # Remove xcconfig files with sensitive data + rm -f MacOS/ProxyBridge/Signing-Config-app.xcconfig || true + rm -f MacOS/ProxyBridge/Signing-Config-ext.xcconfig || true \ No newline at end of file diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index cfd1307..6010e8d 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -5,6 +5,9 @@ on: types: [published, created] workflow_dispatch: +permissions: + contents: write + jobs: build-and-release: runs-on: self-hosted @@ -14,11 +17,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' - - name: Verify WinDivert installation run: | if (Test-Path "C:\WinDivert-2.2.2-A") {