fixing mac pkg signing #14
Workflow file for this run
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 and Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*.*.*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version number (without v prefix)' | |
| required: false | |
| default: '' | |
| skip_store_submission: | |
| description: 'Skip store submissions' | |
| required: false | |
| default: 'false' | |
| type: boolean | |
| permissions: | |
| contents: write | |
| id-token: write # Required for Azure OIDC authentication | |
| env: | |
| NODE_VERSION: '20' | |
| jobs: | |
| build-windows: | |
| name: Build Windows (AppX/MSIX) | |
| runs-on: windows-latest | |
| environment: release # Required for Azure OIDC federated identity | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Setup Python 3.11 for node-gyp | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Get version from tag | |
| id: version | |
| shell: pwsh | |
| run: | | |
| if ("${{ github.ref_type }}" -eq "tag") { | |
| $version = "${{ github.ref_name }}" -replace '^v', '' | |
| } elseif ("${{ github.event.inputs.version }}") { | |
| $version = "${{ github.event.inputs.version }}" | |
| } else { | |
| $version = (Get-Content package.json | ConvertFrom-Json).version | |
| } | |
| echo "VERSION=$version" >> $env:GITHUB_OUTPUT | |
| Write-Host "Building version: $version" | |
| - name: Install dependencies | |
| run: npm ci | |
| timeout-minutes: 15 | |
| - name: Build application | |
| run: npm run private:compile | |
| shell: bash | |
| - name: Build Windows AppX (unsigned) | |
| run: npm run private:build:win | |
| env: | |
| CSC_IDENTITY_AUTO_DISCOVERY: false # Disable auto code signing | |
| # DISABLED: Windows Store will sign the package during submission | |
| # Re-enable these steps when Azure Trusted Signing is configured | |
| # - name: Azure Login for Code Signing | |
| # uses: azure/login@v2 | |
| # with: | |
| # client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| # tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| # subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| # | |
| # - name: Sign Windows AppX with Azure Trusted Signing | |
| # uses: azure/trusted-signing-action@v0.5.0 | |
| # with: | |
| # endpoint: https://eus.codesigning.azure.net/ | |
| # trusted-signing-account-name: Allow2 | |
| # certificate-profile-name: Allow2-Dev-Signing | |
| # files-folder: ${{ github.workspace }}/dist | |
| # files-folder-filter: appx,msix | |
| # file-digest: SHA256 | |
| # timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| # timestamp-digest: SHA256 | |
| # DISABLED: Signature verification skipped since Windows Store will sign | |
| # - name: Verify Windows signature | |
| # shell: pwsh | |
| # run: | | |
| # Write-Host "=== Verifying AppX/MSIX signatures ===" | |
| # | |
| # # Find signtool | |
| # $signtool = Get-ChildItem -Path "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter "signtool.exe" | | |
| # Where-Object { $_.FullName -match "x64" } | | |
| # Sort-Object { [version]($_.FullName -replace '.*\\(\d+\.\d+\.\d+\.\d+)\\.*', '$1') } -Descending | | |
| # Select-Object -First 1 -ExpandProperty FullName | |
| # | |
| # Write-Host "Using signtool: $signtool" | |
| # | |
| # # Verify AppX/MSIX signatures | |
| # Get-ChildItem -Path "dist" -Include "*.appx","*.msix" -Recurse | ForEach-Object { | |
| # Write-Host "Verifying: $($_.Name)" | |
| # & $signtool verify /pa $_.FullName | |
| # if ($LASTEXITCODE -eq 0) { | |
| # Write-Host " Signature valid" | |
| # } else { | |
| # Write-Host " WARNING: Signature verification failed" | |
| # } | |
| # } | |
| - name: List built files | |
| shell: pwsh | |
| run: | | |
| Write-Host "=== Built files ===" | |
| Get-ChildItem -Path "dist" -Recurse -Include "*.appx","*.msix","*.exe" | ForEach-Object { | |
| Write-Host " $($_.FullName) ($([math]::Round($_.Length / 1MB, 2)) MB)" | |
| } | |
| - name: Upload Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-appx | |
| path: | | |
| dist/*.appx | |
| dist/*.msix | |
| retention-days: 90 | |
| build-macos: | |
| name: Build macOS (Mac App Store) | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Setup Python 3.11 for node-gyp | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Get version from tag | |
| id: version | |
| run: | | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| VERSION="${{ github.ref_name }}" | |
| VERSION="${VERSION#v}" | |
| elif [ -n "${{ github.event.inputs.version }}" ]; then | |
| VERSION="${{ github.event.inputs.version }}" | |
| else | |
| VERSION=$(node -p "require('./package.json').version") | |
| fi | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Building version: $VERSION" | |
| - name: Install dependencies | |
| run: npm ci | |
| timeout-minutes: 15 | |
| - name: Import Apple certificates | |
| env: | |
| APPLE_APP_CERT_BASE64: ${{ secrets.APPLE_APP_CERT_BASE64 }} | |
| APPLE_INSTALLER_CERT_BASE64: ${{ secrets.APPLE_INSTALLER_CERT_BASE64 }} | |
| APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| run: | | |
| # Check if certificates are provided | |
| if [ -z "$APPLE_APP_CERT_BASE64" ] && [ -z "$APPLE_INSTALLER_CERT_BASE64" ]; then | |
| echo "⚠️ No certificates provided - skipping code signing" | |
| exit 0 | |
| fi | |
| # Create temporary keychain | |
| KEYCHAIN_PATH="$HOME/Library/Keychains/temp.keychain-db" | |
| KEYCHAIN_PASSWORD="actions" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" temp.keychain | |
| security set-keychain-settings -lut 21600 temp.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" temp.keychain | |
| # Import Application certificate | |
| if [ -n "$APPLE_APP_CERT_BASE64" ]; then | |
| echo "=== Importing 3rd Party Mac Developer Application Certificate ===" | |
| echo "$APPLE_APP_CERT_BASE64" | base64 -d > app_cert.p12 | |
| # Use -A flag to allow ALL applications access (required for CI) | |
| security import app_cert.p12 -k temp.keychain -P "$APPLE_CERT_PASSWORD" -A | |
| rm app_cert.p12 | |
| echo "✅ Application certificate imported with unrestricted access" | |
| fi | |
| # Import Installer certificate with productsign ACL | |
| if [ -n "$APPLE_INSTALLER_CERT_BASE64" ]; then | |
| echo "=== Importing 3rd Party Mac Developer Installer Certificate ===" | |
| echo "$APPLE_INSTALLER_CERT_BASE64" | base64 -d > installer_cert.p12 | |
| # CRITICAL: Use -A flag to allow ALL applications access (required for CI) | |
| # Individual -T flags may not be sufficient on GitHub Actions runners | |
| security import installer_cert.p12 -k temp.keychain -P "$APPLE_CERT_PASSWORD" -A | |
| rm installer_cert.p12 | |
| echo "✅ Installer certificate imported with unrestricted access" | |
| fi | |
| # Download and import Apple WWDR intermediate certificate (required for cert chain validation) | |
| echo "=== Importing Apple WWDR Intermediate Certificate ===" | |
| curl -sL "https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer" -o AppleWWDRCAG3.cer | |
| security import AppleWWDRCAG3.cer -k temp.keychain -T /usr/bin/codesign -T /usr/bin/productsign | |
| rm AppleWWDRCAG3.cer | |
| echo "✅ Apple WWDR G3 intermediate certificate imported" | |
| # Configure keychain partition list for unrestricted access | |
| # The -A flag during import should have set this, but we ensure it here | |
| # Using empty string ("") after -S allows unrestricted access | |
| security set-key-partition-list -S "apple-tool:,apple:,codesign:,productsign:" -s -k "$KEYCHAIN_PASSWORD" temp.keychain 2>/dev/null || { | |
| echo "Warning: Could not set partition list with specific tools, trying unrestricted..." | |
| security set-key-partition-list -S "" -s -k "$KEYCHAIN_PASSWORD" temp.keychain 2>/dev/null || true | |
| } | |
| security default-keychain -s temp.keychain | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" "$HOME/Library/Keychains/login.keychain-db" | |
| echo "" | |
| echo "=== Available signing identities ===" | |
| security find-identity -v temp.keychain | sed 's/\("[^"]*"\)/"***"/g' | |
| echo "" | |
| echo "=== Available installer identities ===" | |
| security find-identity -v temp.keychain | grep -i "installer" | sed 's/\("[^"]*"\)/"***"/g' || echo "No installer identities found" | |
| - name: Build application | |
| run: npm run private:compile | |
| - name: Install provisioning profile | |
| env: | |
| PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }} | |
| run: | | |
| if [ -n "$PROVISIONING_PROFILE_BASE64" ]; then | |
| echo "=== Installing Provisioning Profile ===" | |
| echo "$PROVISIONING_PROFILE_BASE64" | base64 -d > "./Allow2Automate_Distribution.provisionprofile" | |
| # Verify the profile was created | |
| if [ -f "./Allow2Automate_Distribution.provisionprofile" ]; then | |
| echo "✅ Provisioning profile written to project root" | |
| ls -la ./Allow2Automate_Distribution.provisionprofile | |
| else | |
| echo "❌ Failed to create provisioning profile" | |
| exit 1 | |
| fi | |
| # Also install to system location for electron-builder | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| # Extract UUID from profile and install with that name | |
| PROFILE_UUID=$(security cms -D -i "./Allow2Automate_Distribution.provisionprofile" 2>/dev/null | plutil -extract UUID raw - 2>/dev/null || echo "embedded") | |
| if [ "$PROFILE_UUID" != "embedded" ] && [ -n "$PROFILE_UUID" ]; then | |
| cp "./Allow2Automate_Distribution.provisionprofile" ~/Library/MobileDevice/Provisioning\ Profiles/"${PROFILE_UUID}.provisionprofile" | |
| echo "✅ Provisioning profile installed to system location with UUID: ${PROFILE_UUID}" | |
| else | |
| cp "./Allow2Automate_Distribution.provisionprofile" ~/Library/MobileDevice/Provisioning\ Profiles/ | |
| echo "✅ Provisioning profile installed to system location" | |
| fi | |
| else | |
| echo "❌ ERROR: APPLE_PROVISIONING_PROFILE_BASE64 secret not set" | |
| echo "Mac App Store build requires a provisioning profile." | |
| echo "" | |
| echo "To create this secret:" | |
| echo "1. Download your provisioning profile from Apple Developer Portal" | |
| echo "2. Base64 encode it: base64 -i Allow2Automate_Distribution.provisionprofile | pbcopy" | |
| echo "3. Add as GitHub secret: APPLE_PROVISIONING_PROFILE_BASE64" | |
| exit 1 | |
| fi | |
| - name: Build macOS app (MAS target - Universal) | |
| env: | |
| # Application signing certificate (for codesign) | |
| CSC_LINK: ${{ secrets.APPLE_APP_CERT_BASE64 }} | |
| CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| # Installer signing certificate (for productsign) - CRITICAL for MAS PKG | |
| # electron-builder uses these separate env vars because installer certs | |
| # have different Extended Key Usage and aren't found by -p codesigning policy | |
| CSC_INSTALLER_LINK: ${{ secrets.APPLE_INSTALLER_CERT_BASE64 }} | |
| CSC_INSTALLER_KEY_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| APPLE_DEVELOPER_ID: ${{ secrets.APPLE_DEVELOPER_ID }} | |
| APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| run: | | |
| # Ensure keychain is unlocked and in search path for productsign | |
| security unlock-keychain -p "actions" temp.keychain || true | |
| security list-keychains -d user -s "$HOME/Library/Keychains/temp.keychain-db" "$HOME/Library/Keychains/login.keychain-db" | |
| security default-keychain -s temp.keychain | |
| # Verify identities are available | |
| echo "=== Checking for signing identities ===" | |
| echo "Application identities (codesigning policy):" | |
| security find-identity -v -p codesigning temp.keychain | grep -i "3rd Party Mac Developer Application" || echo " No app signing identity found" | |
| echo "" | |
| echo "Installer identities (no policy filter - installer certs have different EKU):" | |
| security find-identity -v temp.keychain | grep -i "3rd Party Mac Developer Installer" || echo " No installer identity found" | |
| # Run the build | |
| npm run private:build:mac | |
| - name: Sign macOS PKG | |
| env: | |
| APPLE_INSTALLER_CERT_BASE64: ${{ secrets.APPLE_INSTALLER_CERT_BASE64 }} | |
| APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| run: | | |
| if [ -z "$APPLE_INSTALLER_CERT_BASE64" ]; then | |
| echo "⚠️ APPLE_INSTALLER_CERT_BASE64 not set - skipping PKG signing" | |
| exit 0 | |
| fi | |
| echo "=== Signing macOS PKG ===" | |
| # CRITICAL: Re-unlock keychain and reset partition list | |
| # The build step takes time and keychain may have locked or partition list may need refresh | |
| echo "Step 1: Unlocking keychain..." | |
| security unlock-keychain -p "actions" temp.keychain | |
| security set-keychain-settings -lut 21600 temp.keychain | |
| # Ensure temp.keychain is in search path and default | |
| security list-keychains -d user -s "$HOME/Library/Keychains/temp.keychain-db" "$HOME/Library/Keychains/login.keychain-db" | |
| security default-keychain -s temp.keychain | |
| # CRITICAL: Re-apply partition list for ALL keys in keychain | |
| # This ensures productsign can access the private key without UI prompts | |
| echo "Step 2: Re-applying partition list for productsign access..." | |
| security set-key-partition-list -S "apple-tool:,apple:,codesign:,productsign:" -s -k "actions" temp.keychain 2>/dev/null || { | |
| echo "Warning: Could not set partition list, trying alternative method..." | |
| # Alternative: allow all partitions | |
| security set-key-partition-list -S "apple-tool:,apple:,codesign:,productsign:,teamid:" -s -k "actions" temp.keychain 2>/dev/null || true | |
| } | |
| # Verify keychain is unlocked and has the right identity | |
| echo "Step 3: Verifying keychain state..." | |
| echo "Keychain list:" | |
| security list-keychains | |
| echo "" | |
| echo "Default keychain:" | |
| security default-keychain | |
| echo "" | |
| echo "Installer identities available:" | |
| security find-identity -v temp.keychain | grep -i "installer" || echo "WARNING: No installer identities found!" | |
| PKG=$(find dist -name "*.pkg" | head -n 1) | |
| if [ -z "$PKG" ]; then | |
| echo "No PKG file found, checking for MAS build..." | |
| PKG=$(find dist/mas -name "*.pkg" 2>/dev/null | head -n 1) | |
| fi | |
| if [ -z "$PKG" ]; then | |
| PKG=$(find dist/mas-universal -name "*.pkg" 2>/dev/null | head -n 1) | |
| fi | |
| if [ -n "$PKG" ]; then | |
| echo "" | |
| echo "Found PKG: $PKG" | |
| echo "PKG size: $(ls -lh "$PKG" | awk '{print $5}')" | |
| # Find installer identity | |
| INSTALLER_IDENTITY=$(security find-identity -v temp.keychain 2>&1 | grep "3rd Party Mac Developer Installer\|Developer ID Installer" | head -1 | sed 's/.*"\(.*\)".*/\1/') | |
| if [ -n "$INSTALLER_IDENTITY" ]; then | |
| echo "Signing with: $(echo "$INSTALLER_IDENTITY" | sed 's/Allow2.*/Allow2 ***/')" | |
| # Final keychain unlock right before signing | |
| security unlock-keychain -p "actions" temp.keychain | |
| echo "" | |
| echo "Step 4: Running productsign..." | |
| # Run productsign with timeout to prevent hanging (macOS compatible) | |
| # 5 minute timeout should be more than enough for any PKG | |
| ( | |
| # Use KEYCHAIN environment variable as fallback | |
| export KEYCHAIN="$HOME/Library/Keychains/temp.keychain-db" | |
| productsign --sign "$INSTALLER_IDENTITY" --keychain temp.keychain "$PKG" "${PKG%.pkg}-signed.pkg" 2>&1 | |
| ) & | |
| SIGN_PID=$! | |
| # Wait up to 300 seconds (5 minutes) | |
| TIMEOUT=300 | |
| while [ $TIMEOUT -gt 0 ]; do | |
| if ! kill -0 $SIGN_PID 2>/dev/null; then | |
| # Process finished | |
| wait $SIGN_PID | |
| EXIT_CODE=$? | |
| break | |
| fi | |
| sleep 1 | |
| TIMEOUT=$((TIMEOUT - 1)) | |
| done | |
| if [ $TIMEOUT -eq 0 ]; then | |
| echo "❌ productsign timed out after 5 minutes" | |
| kill -9 $SIGN_PID 2>/dev/null || true | |
| echo "" | |
| echo "=== DEBUGGING INFO ===" | |
| echo "This usually means keychain access is blocked by macOS security." | |
| echo "" | |
| echo "Keychain info:" | |
| security list-keychains | |
| echo "" | |
| echo "Default keychain:" | |
| security default-keychain | |
| echo "" | |
| echo "Keychain status:" | |
| security show-keychain-info temp.keychain 2>&1 || true | |
| echo "" | |
| echo "Possible causes:" | |
| echo "1. Keychain partition list not set correctly for productsign" | |
| echo "2. Certificate private key requires interactive authorization" | |
| echo "3. Certificate chain validation failing" | |
| exit 1 | |
| elif [ $EXIT_CODE -eq 0 ]; then | |
| mv "${PKG%.pkg}-signed.pkg" "$PKG" | |
| echo "✅ PKG signed successfully" | |
| # Verify signature | |
| echo "" | |
| echo "Verifying signature..." | |
| pkgutil --check-signature "$PKG" || true | |
| else | |
| echo "❌ productsign failed with exit code $EXIT_CODE" | |
| exit 1 | |
| fi | |
| else | |
| echo "⚠️ No installer signing identity found" | |
| echo "Available identities:" | |
| security find-identity -v temp.keychain | |
| fi | |
| else | |
| echo "No PKG file found to sign" | |
| echo "Contents of dist/:" | |
| find dist -type f -name "*.pkg" -o -name "*.app" 2>/dev/null || true | |
| fi | |
| - name: Notarize macOS app | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| if [ -z "$APPLE_ID" ] || [ -z "$APPLE_NOTARIZATION_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then | |
| echo "⚠️ Notarization credentials not set - skipping notarization" | |
| exit 0 | |
| fi | |
| echo "=== Submitting for Notarization ===" | |
| PKG=$(find dist -name "*.pkg" | head -n 1) | |
| if [ -n "$PKG" ]; then | |
| echo "Submitting: $PKG" | |
| SUBMISSION=$(xcrun notarytool submit "$PKG" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait 2>&1) | |
| echo "$SUBMISSION" | |
| if echo "$SUBMISSION" | grep -q "status: Accepted"; then | |
| echo "✅ Notarization accepted" | |
| xcrun stapler staple "$PKG" | |
| echo "✅ Stapled notarization ticket" | |
| else | |
| echo "⚠️ Notarization may have failed - check logs" | |
| SUBMISSION_ID=$(echo "$SUBMISSION" | grep "id:" | head -1 | awk '{print $2}') | |
| if [ -n "$SUBMISSION_ID" ]; then | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" || true | |
| fi | |
| fi | |
| fi | |
| - name: List built files | |
| run: | | |
| echo "=== Built files ===" | |
| find dist -type f \( -name "*.pkg" -o -name "*.dmg" -o -name "*.app" \) -exec ls -lh {} \; | |
| - name: Upload macOS artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-pkg | |
| path: | | |
| dist/*.pkg | |
| dist/*.dmg | |
| dist/mas/*.pkg | |
| retention-days: 90 | |
| build-linux: | |
| name: Build Linux & Publish to Snap Store | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Setup Python 3.11 for node-gyp | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: npm ci | |
| timeout-minutes: 15 | |
| - name: Install Snapcraft | |
| run: sudo snap install snapcraft --classic | |
| - name: Build application | |
| run: npm run private:compile | |
| - name: Build Linux packages | |
| run: npm run private:build:linux | |
| - name: List built files | |
| run: | | |
| echo "=== Built files ===" | |
| find dist -type f \( -name "*.snap" -o -name "*.AppImage" \) -exec ls -lh {} \; | |
| - name: Publish to Snap Store | |
| if: | | |
| startsWith(github.ref, 'refs/tags/') && | |
| !contains(github.ref_name, '-beta') && | |
| !contains(github.ref_name, '-alpha') && | |
| !contains(github.ref_name, '-rc') | |
| env: | |
| SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} | |
| run: | | |
| if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then | |
| echo "=== Snap Store Submission ===" | |
| echo "⚠️ SNAPCRAFT_STORE_CREDENTIALS not configured." | |
| echo "" | |
| echo "To enable automatic publishing:" | |
| echo " 1. Run: snapcraft export-login --snaps=allow2automate --channels=stable,candidate,beta,edge credentials.txt" | |
| echo " 2. Add contents as SNAPCRAFT_STORE_CREDENTIALS secret in GitHub" | |
| exit 0 | |
| fi | |
| SNAP=$(find dist -name "*.snap" | head -n 1) | |
| if [ -z "$SNAP" ]; then | |
| echo "❌ No snap file found" | |
| exit 1 | |
| fi | |
| echo "=== Publishing to Snap Store ===" | |
| echo "Snap: $SNAP" | |
| echo "$SNAPCRAFT_STORE_CREDENTIALS" | snapcraft login --with - | |
| snapcraft upload "$SNAP" --release=stable | |
| echo "✅ Published to Snap Store" | |
| echo "View at: https://snapcraft.io/allow2automate" | |
| - name: Upload Linux artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-packages | |
| path: | | |
| dist/*.snap | |
| dist/*.AppImage | |
| retention-days: 90 | |
| create-release: | |
| name: Create GitHub Release | |
| needs: [build-windows, build-macos, build-linux] | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Get version from tag | |
| id: version | |
| run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT | |
| - name: Prepare release files | |
| run: | | |
| VERSION="${{ steps.version.outputs.VERSION }}" | |
| mkdir -p release-files | |
| echo "=== Artifact directory structure ===" | |
| ls -laR artifacts/ | |
| # Copy Windows files | |
| find artifacts/windows-appx -name "*.appx" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/windows-appx -name "*.msix" -exec cp {} release-files/ \; 2>/dev/null || true | |
| # Copy macOS files | |
| find artifacts/macos-pkg -name "*.pkg" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/macos-pkg -name "*.dmg" -exec cp {} release-files/ \; 2>/dev/null || true | |
| # Copy Linux files | |
| find artifacts/linux-packages -name "*.snap" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/linux-packages -name "*.AppImage" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/linux-packages -name "*.deb" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/linux-packages -name "*.rpm" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find artifacts/linux-packages -name "*.asc" -exec cp {} release-files/ \; 2>/dev/null || true | |
| echo "=== Release files ===" | |
| ls -lh release-files/ | |
| - name: Generate checksums | |
| run: | | |
| cd release-files | |
| sha256sum * > checksums.txt 2>/dev/null || true | |
| cat checksums.txt | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| VERSION="${{ steps.version.outputs.VERSION }}" | |
| PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| CHANGELOG=$(git log --pretty=format:"- %s (%h)" HEAD 2>/dev/null || echo "Initial release") | |
| else | |
| CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..HEAD) | |
| fi | |
| cat > release-notes.md << EOF | |
| ## Allow2 Automate ${VERSION} | |
| ### Installation | |
| #### Windows | |
| - **Microsoft Store**: Search for "Allow2 Automate" in the Microsoft Store | |
| - **Sideload**: Download \`Allow2Automate-*.appx\` and install with PowerShell: | |
| \`\`\`powershell | |
| Add-AppxPackage -Path Allow2Automate-*.appx | |
| \`\`\` | |
| #### macOS | |
| - **Mac App Store**: Search for "Allow2 Automate" in the Mac App Store | |
| - **Direct Install**: Download \`Allow2Automate-*.pkg\` and open to install | |
| #### Linux | |
| - **Snap Store**: \`sudo snap install allow2automate\` | |
| - **AppImage**: Download, make executable (\`chmod +x\`), and run | |
| - **DEB**: \`sudo dpkg -i allow2automate_*.deb\` | |
| - **RPM**: \`sudo rpm -i allow2automate-*.rpm\` | |
| ### Verification | |
| All packages are code-signed and include SHA256 checksums in \`checksums.txt\`. | |
| - **Windows**: Signed with Azure Trusted Signing | |
| - **macOS**: Signed with Apple Developer ID and notarized | |
| - **Linux**: GPG signed (signature files: \`.asc\`) | |
| ### Changes | |
| ${CHANGELOG} | |
| --- | |
| **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${VERSION} | |
| EOF | |
| cat release-notes.md | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: release-files/* | |
| body_path: release-notes.md | |
| draft: false | |
| prerelease: ${{ contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') || contains(github.ref_name, '-rc') }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Automatic Store Submissions | |
| submit-windows-store: | |
| name: Submit to Windows Store | |
| needs: build-windows | |
| runs-on: windows-latest | |
| if: | | |
| startsWith(github.ref, 'refs/tags/') && | |
| !contains(github.ref_name, '-beta') && | |
| !contains(github.ref_name, '-alpha') && | |
| !contains(github.ref_name, '-rc') && | |
| github.event.inputs.skip_store_submission != 'true' | |
| steps: | |
| - name: Download Windows artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-appx | |
| path: artifacts | |
| - name: List artifacts | |
| shell: pwsh | |
| run: | | |
| Write-Host "=== Downloaded artifacts ===" | |
| Get-ChildItem -Path "artifacts" -Recurse | |
| - name: Check Windows Store credentials | |
| id: check-creds | |
| shell: pwsh | |
| env: | |
| STORE_CLIENT_ID: ${{ secrets.WINDOWS_STORE_CLIENT_ID }} | |
| run: | | |
| if (-not [string]::IsNullOrEmpty($env:STORE_CLIENT_ID)) { | |
| echo "HAS_CREDS=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "Windows Store credentials found" | |
| } else { | |
| echo "HAS_CREDS=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "Windows Store credentials not configured" | |
| } | |
| - name: Submit to Microsoft Store | |
| if: steps.check-creds.outputs.HAS_CREDS == 'true' | |
| shell: pwsh | |
| env: | |
| STORE_PRODUCT_ID: ${{ secrets.WINDOWS_STORE_PRODUCT_ID }} | |
| STORE_TENANT_ID: ${{ secrets.WINDOWS_STORE_TENANT_ID }} | |
| STORE_CLIENT_ID: ${{ secrets.WINDOWS_STORE_CLIENT_ID }} | |
| STORE_CLIENT_SECRET: ${{ secrets.WINDOWS_STORE_CLIENT_SECRET }} | |
| run: | | |
| Write-Host "=== Submitting to Microsoft Store ===" | |
| # Find the AppX/MSIX file | |
| $package = Get-ChildItem -Path "artifacts" -Include "*.appx","*.msix" -Recurse | Select-Object -First 1 | |
| if (-not $package) { | |
| Write-Host "❌ No AppX/MSIX package found" | |
| exit 1 | |
| } | |
| Write-Host "Package: $($package.FullName)" | |
| Write-Host "Size: $([math]::Round($package.Length / 1MB, 2)) MB" | |
| # Get access token | |
| Write-Host "Getting access token..." | |
| $tokenUrl = "https://login.microsoftonline.com/$env:STORE_TENANT_ID/oauth2/token" | |
| $body = @{ | |
| grant_type = "client_credentials" | |
| client_id = $env:STORE_CLIENT_ID | |
| client_secret = $env:STORE_CLIENT_SECRET | |
| resource = "https://manage.devcenter.microsoft.com" | |
| } | |
| try { | |
| $tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body | |
| $accessToken = $tokenResponse.access_token | |
| Write-Host "✅ Access token obtained" | |
| } catch { | |
| Write-Host "❌ Failed to get access token: $_" | |
| Write-Host "" | |
| Write-Host "Please verify your Azure AD credentials are correct." | |
| exit 1 | |
| } | |
| # Get current submission | |
| $headers = @{ | |
| Authorization = "Bearer $accessToken" | |
| "Content-Type" = "application/json" | |
| } | |
| $appUrl = "https://manage.devcenter.microsoft.com/v1.0/my/applications/$env:STORE_PRODUCT_ID" | |
| try { | |
| $app = Invoke-RestMethod -Uri $appUrl -Headers $headers -Method Get | |
| Write-Host "✅ Found app: $($app.primaryName)" | |
| } catch { | |
| Write-Host "❌ Failed to find app with ID: $env:STORE_PRODUCT_ID" | |
| Write-Host "Error: $_" | |
| Write-Host "" | |
| Write-Host "Please ensure:" | |
| Write-Host " 1. The product exists in Partner Center" | |
| Write-Host " 2. WINDOWS_STORE_PRODUCT_ID contains the correct Store ID" | |
| Write-Host " 3. Your Azure AD app has access to this product" | |
| Write-Host "" | |
| Write-Host "Manual submission: https://partner.microsoft.com/dashboard" | |
| exit 1 | |
| } | |
| Write-Host "" | |
| Write-Host "=== Submission Instructions ===" | |
| Write-Host "Package built successfully and ready for upload." | |
| Write-Host "" | |
| Write-Host "For first-time submission or major updates:" | |
| Write-Host " 1. Go to: https://partner.microsoft.com/dashboard" | |
| Write-Host " 2. Select your app: $($app.primaryName)" | |
| Write-Host " 3. Create new submission" | |
| Write-Host " 4. Upload: $($package.Name)" | |
| Write-Host "" | |
| Write-Host "The package is available in the workflow artifacts." | |
| - name: Store submission status | |
| if: steps.check-creds.outputs.HAS_CREDS != 'true' | |
| shell: pwsh | |
| run: | | |
| Write-Host "=== Windows Store Submission ===" | |
| Write-Host "Automatic submission not configured." | |
| Write-Host "" | |
| Write-Host "To enable, add these secrets:" | |
| Write-Host " - WINDOWS_STORE_PRODUCT_ID (Store ID from Partner Center)" | |
| Write-Host " - WINDOWS_STORE_CLIENT_ID (Azure AD App Client ID)" | |
| Write-Host " - WINDOWS_STORE_CLIENT_SECRET (Azure AD App Secret)" | |
| Write-Host " - WINDOWS_STORE_TENANT_ID (Azure AD Tenant ID)" | |
| Write-Host "" | |
| Write-Host "First-time setup:" | |
| Write-Host " 1. Create your app in Partner Center first" | |
| Write-Host " 2. Create an Azure AD app and link it to Partner Center" | |
| Write-Host " 3. Add the secrets to your GitHub repository" | |
| Write-Host "" | |
| Write-Host "Manual submission: https://partner.microsoft.com/dashboard" | |
| submit-mac-app-store: | |
| name: Submit to Mac App Store | |
| needs: build-macos | |
| runs-on: macos-latest | |
| if: | | |
| startsWith(github.ref, 'refs/tags/') && | |
| !contains(github.ref_name, '-beta') && | |
| !contains(github.ref_name, '-alpha') && | |
| !contains(github.ref_name, '-rc') && | |
| github.event.inputs.skip_store_submission != 'true' | |
| steps: | |
| - name: Download macOS artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: macos-pkg | |
| path: artifacts | |
| - name: List artifacts | |
| run: | | |
| echo "=== Downloaded artifacts ===" | |
| find artifacts -type f -exec ls -lh {} \; | |
| - name: Submit to Mac App Store | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_PASSWORD" ]; then | |
| echo "=== Mac App Store Submission ===" | |
| echo "Automatic submission not configured." | |
| echo "" | |
| echo "To enable, ensure these secrets are set:" | |
| echo " - APPLE_ID" | |
| echo " - APPLE_NOTARIZATION_PASSWORD (app-specific password)" | |
| echo " - APPLE_TEAM_ID" | |
| echo "" | |
| echo "Manual submission: https://appstoreconnect.apple.com" | |
| exit 0 | |
| fi | |
| echo "=== Submitting to Mac App Store ===" | |
| # Find the MAS PKG (Mac App Store target) | |
| PKG=$(find artifacts -name "*.pkg" | head -n 1) | |
| if [ -z "$PKG" ]; then | |
| echo "❌ No PKG file found" | |
| exit 1 | |
| fi | |
| echo "Uploading: $PKG" | |
| # Use xcrun altool or Transporter to upload | |
| xcrun altool --upload-app \ | |
| --type osx \ | |
| --file "$PKG" \ | |
| --username "$APPLE_ID" \ | |
| --password "$APPLE_APP_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" || { | |
| echo "⚠️ xcrun altool failed, trying Transporter..." | |
| # Alternative: Use Transporter if available | |
| exit 1 | |
| } | |
| echo "✅ Submitted to Mac App Store" | |
| echo "Check status at: https://appstoreconnect.apple.com" | |