Add code signing config for Windows (Azure Artifact Signing) and macOS (Apple notarization)
CI workflows now support code signing when secrets are configured: - macOS: Apple Developer certificate + App Store Connect API key for notarization - Windows: Azure Artifact Signing via signtool + dlib - Both are no-ops when secrets aren't set (backwards-compatible) - Add Entitlements.plist (mic, network) and Info.plist (NSMicrophoneUsageDescription) - Add SIGNING.md with full setup guide for both platforms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,27 @@ jobs:
|
|||||||
- name: Install npm dependencies
|
- name: Install npm dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Setup code signing
|
||||||
|
env:
|
||||||
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }}
|
||||||
|
run: |
|
||||||
|
if [ -n "${APPLE_API_KEY_CONTENT}" ]; then
|
||||||
|
echo "Setting up notarization API key..."
|
||||||
|
mkdir -p ~/private_keys
|
||||||
|
echo "${APPLE_API_KEY_CONTENT}" > ~/private_keys/AuthKey_${APPLE_API_KEY}.p8
|
||||||
|
else
|
||||||
|
echo "No signing secrets configured, skipping code signing setup"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build Tauri app
|
- name: Build Tauri app
|
||||||
|
env:
|
||||||
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
|
APPLE_API_KEY_PATH: ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8
|
||||||
run: npm run tauri build
|
run: npm run tauri build
|
||||||
|
|
||||||
- name: Upload to release
|
- name: Upload to release
|
||||||
@@ -91,3 +111,6 @@ jobs:
|
|||||||
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
||||||
echo "Upload response: HTTP ${HTTP_CODE}"
|
echo "Upload response: HTTP ${HTTP_CODE}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Cleanup signing artifacts
|
||||||
|
run: rm -rf ~/private_keys
|
||||||
|
|||||||
@@ -46,8 +46,45 @@ jobs:
|
|||||||
shell: powershell
|
shell: powershell
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Setup Azure Artifact Signing
|
||||||
|
shell: powershell
|
||||||
|
env:
|
||||||
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }}
|
||||||
|
AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
|
||||||
|
AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }}
|
||||||
|
run: |
|
||||||
|
if (-not $env:AZURE_CLIENT_ID) {
|
||||||
|
Write-Host "No Azure signing secrets configured, skipping code signing setup"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Setting up Azure Artifact Signing..."
|
||||||
|
|
||||||
|
# Install Artifact Signing client tools
|
||||||
|
nuget install Microsoft.ArtifactSigning.Client -x -OutputDirectory .\signing-tools
|
||||||
|
$dlibPath = (Resolve-Path ".\signing-tools\Microsoft.ArtifactSigning.Client*\bin\x64\Azure.CodeSigning.Dlib.dll").Path
|
||||||
|
|
||||||
|
# Write metadata.json
|
||||||
|
@{
|
||||||
|
Endpoint = $env:AZURE_SIGNING_ENDPOINT
|
||||||
|
CodeSigningAccountName = $env:AZURE_SIGNING_ACCOUNT
|
||||||
|
CertificateProfileName = $env:AZURE_CERT_PROFILE
|
||||||
|
} | ConvertTo-Json | Out-File -Encoding UTF8 metadata.json
|
||||||
|
$metadataPath = (Resolve-Path "metadata.json").Path
|
||||||
|
|
||||||
|
# Inject signCommand into tauri.conf.json for this build
|
||||||
|
$conf = Get-Content src-tauri\tauri.conf.json -Raw | ConvertFrom-Json
|
||||||
|
$signCmd = "signtool.exe sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 /dlib `"$dlibPath`" /dmdf `"$metadataPath`" %1"
|
||||||
|
$conf.bundle.windows | Add-Member -NotePropertyName "signCommand" -NotePropertyValue $signCmd -Force
|
||||||
|
$conf | ConvertTo-Json -Depth 10 | Set-Content src-tauri\tauri.conf.json -Encoding UTF8
|
||||||
|
|
||||||
- name: Build Tauri app
|
- name: Build Tauri app
|
||||||
shell: powershell
|
shell: powershell
|
||||||
|
env:
|
||||||
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
run: npm run tauri build
|
run: npm run tauri build
|
||||||
|
|
||||||
- name: Upload to release
|
- name: Upload to release
|
||||||
|
|||||||
20
CLAUDE.md
20
CLAUDE.md
@@ -275,9 +275,29 @@ All per-OS build workflows can be re-run independently via `workflow_dispatch` w
|
|||||||
- `Info.plist` must include `NSMicrophoneUsageDescription` for mic access
|
- `Info.plist` must include `NSMicrophoneUsageDescription` for mic access
|
||||||
- No CUDA builds — CPU/MPS only
|
- No CUDA builds — CPU/MPS only
|
||||||
|
|
||||||
|
## Code Signing
|
||||||
|
|
||||||
|
Code signing is configured for Windows and macOS to eliminate install warnings (SmartScreen / Gatekeeper). See [SIGNING.md](SIGNING.md) for full setup details.
|
||||||
|
|
||||||
|
**Status (as of 2026-04-10):** CI workflow changes are committed. Waiting on identity verification for both platforms before secrets can be configured.
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- macOS: Tauri auto-signs when `APPLE_CERTIFICATE` and related env vars are set in CI. Notarization uses App Store Connect API key.
|
||||||
|
- Windows: Azure Artifact Signing via `signtool.exe` + dlib. CI workflow injects `signCommand` into `tauri.conf.json` at build time when `AZURE_CLIENT_ID` is set.
|
||||||
|
- Both are no-ops when secrets aren't configured — unsigned builds work as before.
|
||||||
|
|
||||||
|
**Key files:**
|
||||||
|
- `src-tauri/Entitlements.plist` — macOS hardened runtime entitlements (mic, network)
|
||||||
|
- `src-tauri/Info.plist` — macOS microphone usage description
|
||||||
|
- `.gitea/workflows/build-app-macos.yml` — Apple signing + notarization
|
||||||
|
- `.gitea/workflows/build-app-windows.yml` — Azure Artifact Signing
|
||||||
|
|
||||||
|
**Secrets required (12 total):** See [SIGNING.md](SIGNING.md) for the full list — 6 Apple secrets, 6 Azure secrets.
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- [README.md](README.md) — User-facing documentation
|
- [README.md](README.md) — User-facing documentation
|
||||||
- [BUILD.md](BUILD.md) — Detailed build instructions
|
- [BUILD.md](BUILD.md) — Detailed build instructions
|
||||||
- [INSTALL.md](INSTALL.md) — Installation guide
|
- [INSTALL.md](INSTALL.md) — Installation guide
|
||||||
|
- [SIGNING.md](SIGNING.md) — Code signing setup guide
|
||||||
- [server/nodejs/README.md](server/nodejs/README.md) — Node.js server setup
|
- [server/nodejs/README.md](server/nodejs/README.md) — Node.js server setup
|
||||||
|
|||||||
136
SIGNING.md
Normal file
136
SIGNING.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Code Signing Setup
|
||||||
|
|
||||||
|
This document explains how to configure code signing for Local Transcription so that Windows and macOS installers are trusted by the operating system.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Without code signing:
|
||||||
|
- **Windows**: SmartScreen shows "Windows protected your PC" warnings
|
||||||
|
- **macOS**: Gatekeeper blocks the app — "app can't be opened because it is from an unidentified developer"
|
||||||
|
|
||||||
|
The CI/CD workflows are configured to sign automatically when the required secrets are present. Without secrets, builds still work — they just produce unsigned installers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Windows — Azure Artifact Signing
|
||||||
|
|
||||||
|
**Cost**: ~$9.99/month (up to 5,000 signatures)
|
||||||
|
|
||||||
|
### 1. Create an Azure Account
|
||||||
|
|
||||||
|
Sign up at https://azure.microsoft.com if you don't already have one.
|
||||||
|
|
||||||
|
### 2. Set Up Artifact Signing
|
||||||
|
|
||||||
|
1. In the Azure Portal, search for **Artifact Signing**
|
||||||
|
2. Create a new **Artifact Signing Account**
|
||||||
|
- Choose a region (e.g., West US 2) — note this for the endpoint URL
|
||||||
|
- The endpoint will be like `https://wus2.codesigning.azure.net/`
|
||||||
|
3. Complete **Identity Verification** (required before you can create certificate profiles)
|
||||||
|
4. Create a **Certificate Profile** with type "Public Trust" for code signing
|
||||||
|
|
||||||
|
### 3. Create an App Registration (Service Principal)
|
||||||
|
|
||||||
|
This allows CI to authenticate to Azure:
|
||||||
|
|
||||||
|
1. Go to **Azure Active Directory** > **App registrations** > **New registration**
|
||||||
|
2. Name it (e.g., `local-transcription-signing`)
|
||||||
|
3. After creation, note the **Application (client) ID** and **Directory (tenant) ID**
|
||||||
|
4. Go to **Certificates & secrets** > **New client secret** — note the secret value
|
||||||
|
5. Grant the app registration the **Artifact Signing Certificate Profile Signer** role on your Artifact Signing Account
|
||||||
|
|
||||||
|
### 4. Add Gitea Secrets
|
||||||
|
|
||||||
|
In your Gitea repository, go to **Settings** > **Actions** > **Secrets** and add:
|
||||||
|
|
||||||
|
| Secret Name | Value |
|
||||||
|
|-------------|-------|
|
||||||
|
| `AZURE_CLIENT_ID` | App registration Application (client) ID |
|
||||||
|
| `AZURE_CLIENT_SECRET` | App registration client secret value |
|
||||||
|
| `AZURE_TENANT_ID` | Directory (tenant) ID |
|
||||||
|
| `AZURE_SIGNING_ENDPOINT` | Artifact Signing endpoint URL (e.g., `https://wus2.codesigning.azure.net/`) |
|
||||||
|
| `AZURE_SIGNING_ACCOUNT` | Artifact Signing account name |
|
||||||
|
| `AZURE_CERT_PROFILE` | Certificate profile name |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## macOS — Apple Developer Code Signing + Notarization
|
||||||
|
|
||||||
|
**Cost**: $99/year (Apple Developer Program)
|
||||||
|
|
||||||
|
### 1. Enroll in the Apple Developer Program
|
||||||
|
|
||||||
|
Sign up at https://developer.apple.com/programs/
|
||||||
|
|
||||||
|
### 2. Create a Developer ID Certificate
|
||||||
|
|
||||||
|
1. Open **Xcode** > **Settings** > **Accounts** > select your team > **Manage Certificates**
|
||||||
|
2. Click **+** > **Developer ID Application**
|
||||||
|
3. Or create via the Apple Developer portal: **Certificates, Identifiers & Profiles** > **Certificates** > **+** > **Developer ID Application**
|
||||||
|
|
||||||
|
### 3. Export the Certificate as .p12
|
||||||
|
|
||||||
|
1. Open **Keychain Access**
|
||||||
|
2. Find your **Developer ID Application** certificate
|
||||||
|
3. Right-click > **Export** > save as `.p12` with a password
|
||||||
|
4. Base64-encode it:
|
||||||
|
```bash
|
||||||
|
base64 -i certificate.p12 | tr -d '\n'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create an App Store Connect API Key
|
||||||
|
|
||||||
|
This is used for notarization (submitting the app to Apple for verification):
|
||||||
|
|
||||||
|
1. Go to https://appstoreconnect.apple.com/access/integrations/api
|
||||||
|
2. Click **Generate API Key**
|
||||||
|
3. Give it a name and **Developer** role (minimum)
|
||||||
|
4. Download the `.p8` private key file (you can only download it once)
|
||||||
|
5. Note the **Key ID** and **Issuer ID** shown on the page
|
||||||
|
|
||||||
|
### 5. Find Your Signing Identity
|
||||||
|
|
||||||
|
Your signing identity looks like:
|
||||||
|
```
|
||||||
|
Developer ID Application: Your Name (TEAMID)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find it by running:
|
||||||
|
```bash
|
||||||
|
security find-identity -v -p codesigning
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Add Gitea Secrets
|
||||||
|
|
||||||
|
| Secret Name | Value |
|
||||||
|
|-------------|-------|
|
||||||
|
| `APPLE_CERTIFICATE` | Base64-encoded .p12 certificate (from step 3) |
|
||||||
|
| `APPLE_CERTIFICATE_PASSWORD` | Password used when exporting the .p12 |
|
||||||
|
| `APPLE_SIGNING_IDENTITY` | Full identity string (e.g., `Developer ID Application: Your Name (TEAMID)`) |
|
||||||
|
| `APPLE_API_KEY` | App Store Connect API Key ID |
|
||||||
|
| `APPLE_API_ISSUER` | API issuer UUID |
|
||||||
|
| `APPLE_API_KEY_CONTENT` | Full contents of the `.p8` private key file |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifying Signing Works
|
||||||
|
|
||||||
|
### Trigger a Build
|
||||||
|
|
||||||
|
Both build workflows use `workflow_dispatch`, so you can trigger them manually in Gitea:
|
||||||
|
|
||||||
|
1. Go to **Actions** > select the workflow > **Run workflow**
|
||||||
|
2. Enter the release tag (e.g., `v2.0.15`)
|
||||||
|
|
||||||
|
### Check macOS
|
||||||
|
|
||||||
|
After installing the `.dmg`, the app should open without any Gatekeeper warnings. You can also verify from the command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
codesign -dv --verbose=4 /Applications/Local\ Transcription.app
|
||||||
|
spctl --assess --type execute /Applications/Local\ Transcription.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Windows
|
||||||
|
|
||||||
|
After running the `.msi` or `-setup.exe`, there should be no SmartScreen warning. The installer properties should show your organization name as the publisher.
|
||||||
14
src-tauri/Entitlements.plist
Normal file
14
src-tauri/Entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
8
src-tauri/Info.plist
Normal file
8
src-tauri/Info.plist
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Local Transcription needs microphone access for real-time speech-to-text transcription.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -33,7 +33,14 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico",
|
"icons/icon.ico",
|
||||||
"icons/icon.png"
|
"icons/icon.png"
|
||||||
]
|
],
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": "Entitlements.plist",
|
||||||
|
"hardenedRuntime": true
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"digestAlgorithm": "sha256"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"shell": {
|
"shell": {
|
||||||
|
|||||||
Reference in New Issue
Block a user