swift-apple-intelligence-grpc/docs/pipeline-configuration.md
Mathias Beaulieu-Duncan 8e53dee03c Add CI/CD pipeline and documentation
- Add Gitea Actions workflow for automated releases
  - Builds release binary
  - Signs app with Developer ID
  - Creates and signs DMG
  - Notarizes with Apple
  - Uploads to release

- Add documentation:
  - macos-runner-setup.md: Self-hosted runner setup guide
  - pipeline-configuration.md: Secrets and pipeline config guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 04:35:36 -05:00

295 lines
7.2 KiB
Markdown

# 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 <submission-id>` 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