diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
new file mode 100644
index 0000000..2ca574b
--- /dev/null
+++ b/.gitea/workflows/release.yml
@@ -0,0 +1,150 @@
+name: Build and Release
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Swift
+ uses: swift-actions/setup-swift@v2
+ with:
+ swift-version: "6.0"
+
+ - name: Install Certificate
+ env:
+ APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
+ APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ run: |
+ # Create temporary keychain
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
+
+ security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+ security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+
+ # Import certificate
+ CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
+ echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
+
+ security import "$CERTIFICATE_PATH" \
+ -P "$APPLE_CERTIFICATE_PASSWORD" \
+ -A -t cert -f pkcs12 \
+ -k "$KEYCHAIN_PATH"
+
+ security list-keychain -d user -s "$KEYCHAIN_PATH"
+
+ # Allow codesign to access keychain
+ security set-key-partition-list -S apple-tool:,apple:,codesign: \
+ -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+
+ - name: Build Release Binary
+ run: swift build -c release --product AppleIntelligenceApp
+
+ - name: Create App Bundle
+ run: |
+ APP_NAME="Apple Intelligence Server"
+ VERSION="${GITHUB_REF_NAME#v}"
+
+ mkdir -p "dist/$APP_NAME.app/Contents/MacOS"
+ mkdir -p "dist/$APP_NAME.app/Contents/Resources"
+
+ cp .build/release/AppleIntelligenceApp "dist/$APP_NAME.app/Contents/MacOS/$APP_NAME"
+
+ cat > "dist/$APP_NAME.app/Contents/Info.plist" << EOF
+
+
+
+
+ CFBundleExecutable
+ $APP_NAME
+ CFBundleIdentifier
+ com.svrnty.apple-intelligence-server
+ CFBundleName
+ $APP_NAME
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $VERSION
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ 26.0
+ LSUIElement
+
+ NSHighResolutionCapable
+
+ NSLocalNetworkUsageDescription
+ Apple Intelligence Server needs local network access to accept connections from other devices.
+ NSPrincipalClass
+ NSApplication
+
+
+ EOF
+
+ echo -n "APPL????" > "dist/$APP_NAME.app/Contents/PkgInfo"
+
+ - name: Sign App
+ run: |
+ codesign --deep --force --verify --verbose \
+ --options runtime \
+ --sign "Developer ID Application: Mathias Beaulieu-Duncan (LD76P8L42W)" \
+ "dist/Apple Intelligence Server.app"
+
+ - name: Create DMG
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+
+ mkdir -p dist/dmg-temp
+ cp -R "dist/Apple Intelligence Server.app" dist/dmg-temp/
+ ln -s /Applications dist/dmg-temp/Applications
+
+ hdiutil create -volname "Apple Intelligence Server" \
+ -srcfolder dist/dmg-temp \
+ -ov -format UDRW dist/temp.dmg
+
+ hdiutil convert dist/temp.dmg -format UDZO \
+ -o "dist/AppleIntelligenceServer-$VERSION.dmg"
+
+ rm -rf dist/dmg-temp dist/temp.dmg
+
+ - name: Sign DMG
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+ codesign --force \
+ --sign "Developer ID Application: Mathias Beaulieu-Duncan (LD76P8L42W)" \
+ "dist/AppleIntelligenceServer-$VERSION.dmg"
+
+ - name: Notarize DMG
+ env:
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+
+ xcrun notarytool submit "dist/AppleIntelligenceServer-$VERSION.dmg" \
+ --apple-id "$APPLE_ID" \
+ --password "$APPLE_APP_PASSWORD" \
+ --team-id "$APPLE_TEAM_ID" \
+ --wait
+
+ - name: Staple DMG
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+ xcrun stapler staple "dist/AppleIntelligenceServer-$VERSION.dmg"
+
+ - name: Upload to Release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+ gh release upload "$GITHUB_REF_NAME" \
+ "dist/AppleIntelligenceServer-$VERSION.dmg" \
+ --clobber
diff --git a/docs/macos-runner-setup.md b/docs/macos-runner-setup.md
new file mode 100644
index 0000000..94ac67a
--- /dev/null
+++ b/docs/macos-runner-setup.md
@@ -0,0 +1,187 @@
+# Setting Up a macOS Self-Hosted Runner for Gitea Actions
+
+This guide explains how to set up a self-hosted Gitea Actions runner on macOS for building, signing, and notarizing the Apple Intelligence Server app.
+
+## Prerequisites
+
+- A Mac (Intel or Apple Silicon) running macOS 13+
+- Admin access to your Gitea instance
+- Xcode Command Line Tools installed
+
+## Step 1: Install Xcode Command Line Tools
+
+```bash
+xcode-select --install
+```
+
+## Step 2: Install Swift
+
+Ensure Swift 6.0+ is installed:
+
+```bash
+swift --version
+```
+
+If not installed, download from [swift.org](https://swift.org/download/) or install via Xcode.
+
+## Step 3: Download Gitea Act Runner
+
+Download the latest release from [gitea/act_runner](https://gitea.com/gitea/act_runner/releases):
+
+```bash
+# For Apple Silicon (M1/M2/M3)
+curl -L -o act_runner https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-darwin-arm64
+
+# For Intel Mac
+curl -L -o act_runner https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-darwin-amd64
+
+# Make executable
+chmod +x act_runner
+```
+
+## Step 4: Get Registration Token
+
+1. Go to your Gitea repository
+2. Navigate to **Settings** → **Actions** → **Runners**
+3. Click **Create new Runner**
+4. Copy the registration token
+
+## Step 5: Register the Runner
+
+```bash
+./act_runner register \
+ --instance https://your-gitea-instance.com \
+ --token YOUR_REGISTRATION_TOKEN \
+ --name "macos-runner" \
+ --labels "macos,macos-latest,self-hosted"
+```
+
+When prompted:
+- **Runner name:** `macos-runner` (or any name you prefer)
+- **Labels:** `macos,macos-latest,self-hosted`
+
+This creates a `.runner` configuration file in the current directory.
+
+## Step 6: Run the Runner
+
+### Option A: Run in Foreground (for testing)
+
+```bash
+./act_runner daemon
+```
+
+### Option B: Run as a Background Service (recommended)
+
+Create a LaunchAgent to run the runner automatically:
+
+```bash
+mkdir -p ~/Library/LaunchAgents
+```
+
+Create the plist file:
+
+```bash
+cat > ~/Library/LaunchAgents/com.gitea.act_runner.plist << 'EOF'
+
+
+
+
+ Label
+ com.gitea.act_runner
+ ProgramArguments
+
+ /Users/YOUR_USERNAME/act_runner/act_runner
+ daemon
+
+ WorkingDirectory
+ /Users/YOUR_USERNAME/act_runner
+ RunAtLoad
+
+ KeepAlive
+
+ StandardOutPath
+ /Users/YOUR_USERNAME/act_runner/runner.log
+ StandardErrorPath
+ /Users/YOUR_USERNAME/act_runner/runner.error.log
+
+
+EOF
+```
+
+**Important:** Replace `YOUR_USERNAME` with your actual macOS username.
+
+Load the service:
+
+```bash
+launchctl load ~/Library/LaunchAgents/com.gitea.act_runner.plist
+```
+
+## Step 7: Verify Runner is Connected
+
+1. Go to your Gitea repository
+2. Navigate to **Settings** → **Actions** → **Runners**
+3. Your runner should appear as **Online**
+
+## Managing the Runner
+
+### Check Status
+
+```bash
+launchctl list | grep act_runner
+```
+
+### Stop the Runner
+
+```bash
+launchctl unload ~/Library/LaunchAgents/com.gitea.act_runner.plist
+```
+
+### Start the Runner
+
+```bash
+launchctl load ~/Library/LaunchAgents/com.gitea.act_runner.plist
+```
+
+### View Logs
+
+```bash
+tail -f ~/act_runner/runner.log
+tail -f ~/act_runner/runner.error.log
+```
+
+## Security Considerations
+
+1. **Dedicated User:** Consider creating a dedicated macOS user for the runner
+2. **Keychain Access:** The runner needs access to the keychain for code signing
+3. **Network:** Ensure the Mac has reliable network access to your Gitea instance
+4. **Updates:** Keep the runner updated to the latest version
+
+## Troubleshooting
+
+### Runner Not Connecting
+
+- Check firewall settings
+- Verify the Gitea instance URL is correct
+- Ensure the registration token hasn't expired
+
+### Code Signing Fails
+
+- Ensure certificates are installed in the login keychain
+- Check that the runner has keychain access
+- Verify certificate names match the workflow
+
+### Build Fails with Swift Errors
+
+- Ensure Xcode Command Line Tools are installed
+- Check Swift version compatibility
+- Clear the build cache: `swift package clean`
+
+## Recommended Directory Structure
+
+```
+~/act_runner/
+├── act_runner # The runner binary
+├── .runner # Runner configuration
+├── runner.log # Standard output log
+└── runner.error.log # Error log
+```
diff --git a/docs/pipeline-configuration.md b/docs/pipeline-configuration.md
new file mode 100644
index 0000000..8a6540a
--- /dev/null
+++ b/docs/pipeline-configuration.md
@@ -0,0 +1,294 @@
+# 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