# CI/CD Pipeline Configuration Guide This guide explains how to configure the Gitea Actions pipeline for automated building, signing, and notarizing the Apple Intelligence Server app. ## Overview The pipeline automatically: 1. Builds the app when a release is created 2. Creates a signed `.app` bundle 3. Packages it into a DMG 4. Signs and notarizes the DMG with Apple 5. Uploads the DMG to the release ## Prerequisites - A macOS self-hosted runner (see [macos-runner-setup.md](./macos-runner-setup.md)) - Apple Developer Program membership ($99/year) - Developer ID Application certificate ## Step 1: Export Your Signing Certificate ### Using Keychain Access (GUI) 1. Open **Keychain Access** 2. Find "Developer ID Application: Your Name" 3. Right-click → **Export** 4. Save as `.p12` file 5. Set a strong password ### Using Command Line ```bash security find-identity -v -p codesigning | grep "Developer ID" # Note the certificate name security export -k ~/Library/Keychains/login.keychain-db \ -t identities \ -f pkcs12 \ -P "YOUR_SECURE_PASSWORD" \ -o ~/Desktop/developer-id.p12 ``` ### Convert to Base64 ```bash base64 -i ~/Desktop/developer-id.p12 | tr -d '\n' > ~/Desktop/certificate-base64.txt ``` Copy the contents of `certificate-base64.txt` for the next step. ## Step 2: Create an App-Specific Password Apple requires an app-specific password for notarization: 1. Go to [appleid.apple.com](https://appleid.apple.com/account/manage) 2. Sign in with your Apple ID 3. Go to **Sign-In and Security** → **App-Specific Passwords** 4. Click **Generate an app-specific password** 5. Name it "Gitea CI" or similar 6. Copy the generated password (format: `xxxx-xxxx-xxxx-xxxx`) ## Step 3: Configure Gitea Secrets Go to your Gitea repository → **Settings** → **Actions** → **Secrets** Add the following secrets: | Secret Name | Description | Example | |-------------|-------------|---------| | `APPLE_CERTIFICATE_BASE64` | Base64-encoded .p12 certificate | `MIIKYgIBAzCCCh...` | | `APPLE_CERTIFICATE_PASSWORD` | Password for the .p12 file | `your-p12-password` | | `APPLE_ID` | Your Apple ID email | `you@example.com` | | `APPLE_APP_PASSWORD` | App-specific password | `xxxx-xxxx-xxxx-xxxx` | | `APPLE_TEAM_ID` | Your Apple Developer Team ID | `LD76P8L42W` | ### Finding Your Team ID Your Team ID is shown in: - Apple Developer portal → Membership → Team ID - Or in your certificate name: "Developer ID Application: Name (TEAM_ID)" ## Step 4: Workflow File The workflow file is located at `.gitea/workflows/release.yml`. ### Workflow Triggers The workflow triggers when a release is created: ```yaml on: release: types: [created] ``` ### Key Steps Explained #### Install Certificate Creates a temporary keychain and imports the signing certificate: ```yaml - name: Install Certificate env: APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} run: | # Creates temporary keychain # Imports certificate # Configures codesign access ``` #### Build Release Binary Compiles the Swift project in release mode: ```yaml - name: Build Release Binary run: swift build -c release --product AppleIntelligenceApp ``` #### Sign App Signs the app bundle with hardened runtime: ```yaml - name: Sign App run: | codesign --deep --force --verify --verbose \ --options runtime \ --sign "Developer ID Application: Your Name (TEAM_ID)" \ "dist/Apple Intelligence Server.app" ``` The `--options runtime` flag enables hardened runtime, required for notarization. #### Notarize DMG Submits the DMG to Apple for notarization: ```yaml - name: Notarize DMG run: | xcrun notarytool submit "dist/AppleIntelligenceServer-$VERSION.dmg" \ --apple-id "$APPLE_ID" \ --password "$APPLE_APP_PASSWORD" \ --team-id "$APPLE_TEAM_ID" \ --wait ``` The `--wait` flag makes the command wait until notarization completes. #### Staple DMG Attaches the notarization ticket to the DMG: ```yaml - name: Staple DMG run: xcrun stapler staple "dist/AppleIntelligenceServer-$VERSION.dmg" ``` ## Step 5: Creating a Release To trigger the pipeline: 1. Go to your Gitea repository 2. Click **Releases** → **New Release** 3. Create a tag (e.g., `v1.0.0`) 4. Fill in release title and description 5. Click **Publish Release** The pipeline will automatically: - Build the app - Sign and notarize - Upload the DMG to the release ## Version Numbering The workflow extracts the version from the Git tag: ```yaml VERSION="${GITHUB_REF_NAME#v}" # v1.0.0 → 1.0.0 ``` Tag your releases as `v1.0.0`, `v1.1.0`, etc. ## Monitoring Pipeline Runs 1. Go to your Gitea repository 2. Click **Actions** 3. View the running or completed workflows 4. Click on a run to see detailed logs ## Troubleshooting ### Certificate Import Fails **Error:** `security: SecKeychainItemImport: The specified item already exists in the keychain` **Solution:** The temporary keychain may not be cleaning up. Check the workflow logs. ### Notarization Fails **Error:** `Error: Unable to upload your app for notarization` **Solutions:** - Verify Apple ID credentials are correct - Ensure app-specific password is valid - Check that the app is properly signed with hardened runtime ### Notarization Rejected **Error:** `Package Invalid` **Solutions:** - Run `xcrun notarytool log ` to see details - Common issues: - Missing hardened runtime (`--options runtime`) - Unsigned nested code - Invalid entitlements ### Code Signing Fails **Error:** `No identity found` **Solutions:** - Verify certificate is correctly base64-encoded - Check certificate password is correct - Ensure certificate hasn't expired ## Security Best Practices 1. **Rotate Secrets Regularly:** Update your app-specific password periodically 2. **Certificate Expiration:** Developer ID certificates expire after 5 years 3. **Limit Access:** Only admins should have access to repository secrets 4. **Audit Logs:** Monitor pipeline runs for unauthorized activity ## Customizing the Pipeline ### Adding Tests ```yaml - name: Run Tests run: swift test ``` ### Building for Multiple Architectures ```yaml - name: Build Universal Binary run: | swift build -c release --arch arm64 --arch x86_64 ``` ### Custom DMG Background Add a background image to make the DMG look professional: ```yaml - name: Create DMG with Background run: | # Use create-dmg tool for custom styling brew install create-dmg create-dmg \ --volname "Apple Intelligence Server" \ --background "assets/dmg-background.png" \ --window-pos 200 120 \ --window-size 600 400 \ --icon-size 100 \ --icon "Apple Intelligence Server.app" 150 190 \ --app-drop-link 450 185 \ "dist/AppleIntelligenceServer-$VERSION.dmg" \ "dist/Apple Intelligence Server.app" ``` ## Quick Reference ### Secrets Checklist - [ ] `APPLE_CERTIFICATE_BASE64` - [ ] `APPLE_CERTIFICATE_PASSWORD` - [ ] `APPLE_ID` - [ ] `APPLE_APP_PASSWORD` - [ ] `APPLE_TEAM_ID` ### Release Checklist - [ ] Code is tested and ready - [ ] Version number updated (if applicable) - [ ] Create Git tag: `git tag v1.0.0` - [ ] Push tag: `git push origin v1.0.0` - [ ] Create release on Gitea - [ ] Monitor pipeline completion - [ ] Verify DMG is uploaded to release