wraith/.gitea/workflows/build-release.yml
Vantz Stockwell d98600a319
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 3m39s
fix: MCP bridge built, signed, and shipped in CI releases
- Removed useless CLAUDE_MCP_SERVERS env var injection (doesn't work)
- CI builds wraith-mcp-bridge.exe as a separate cargo --bin step
- Bridge binary signed with EV cert alongside the installer
- Uploaded to Gitea packages per version
- Attached to Gitea release as a downloadable asset
- Users add to PATH then: claude mcp add wraith -- wraith-mcp-bridge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:55:17 -04:00

223 lines
10 KiB
YAML

# =============================================================================
# Wraith — Build & Sign Release (Tauri v2)
# =============================================================================
name: Build & Sign Wraith
on:
push:
tags:
- 'v*'
workflow_dispatch:
env:
EXTRA_PATH: C:\Program Files (x86)\NSIS;C:\Program Files\Eclipse Adoptium\jre-21.0.10.7-hotspot\bin;C:\Users\vantz\.cargo\bin;C:\Users\vantz\.rustup\toolchains\stable-x86_64-pc-windows-msvc\bin;C:\Program Files\nodejs
jobs:
build-and-sign:
name: Build Windows + Sign
runs-on: windows
steps:
- name: Checkout code
shell: powershell
run: |
git clone --depth 1 --branch ${{ github.ref_name }} https://${{ secrets.GIT_TOKEN }}@git.command.vigilcyber.com/vstockwell/wraith.git .
- name: Configure Rust
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
$ErrorActionPreference = "Continue"
rustup default stable
$ErrorActionPreference = "Stop"
- name: Verify toolchain
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
node --version
rustc --version
cargo --version
java --version
- name: Patch version from git tag
shell: powershell
run: |
$ver = ("${{ github.ref_name }}" -replace '^v','')
$conf = Get-Content src-tauri\tauri.conf.json -Raw
$conf = $conf -replace '"version":\s*"[^"]*"', "`"version`": `"$ver`""
[System.IO.File]::WriteAllText((Join-Path (Get-Location) "src-tauri\tauri.conf.json"), $conf)
Write-Host "Patched tauri.conf.json version to $ver"
- name: Install dependencies and build frontend
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
npm ci
npm run build
- name: Install Tauri CLI
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
cargo install tauri-cli --version "^2"
- name: Build Tauri app (with update signing)
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
$env:TAURI_SIGNING_PRIVATE_KEY = "${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}"
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}"
cargo tauri build
Write-Host "=== Build output ==="
Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*
- name: Build and package MCP bridge binary
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
cd src-tauri
cargo build --release --bin wraith-mcp-bridge
Write-Host "Bridge binary built:"
Get-ChildItem target\release\wraith-mcp-bridge.exe
- name: Download jsign
shell: powershell
run: |
Invoke-WebRequest -Uri "https://github.com/ebourg/jsign/releases/download/7.0/jsign-7.0.jar" -OutFile jsign.jar
- name: Get Azure token
shell: powershell
run: |
$body = @{
client_id = "${{ secrets.AZURE_CLIENT_ID }}"
client_secret = "${{ secrets.AZURE_CLIENT_SECRET }}"
scope = "https://vault.azure.net/.default"
grant_type = "client_credentials"
}
$resp = Invoke-RestMethod -Uri "https://login.microsoftonline.com/${{ secrets.AZURE_TENANT_ID }}/oauth2/v2.0/token" -Method POST -Body $body
$token = $resp.access_token
echo "::add-mask::$token"
[System.IO.File]::WriteAllText("$env:TEMP\aztoken.txt", $token)
- name: Sign binaries
shell: powershell
run: |
$env:Path = "$env:EXTRA_PATH;$env:Path"
$token = [System.IO.File]::ReadAllText("$env:TEMP\aztoken.txt")
# Sign NSIS installers + MCP bridge binary
$binaries = @()
$binaries += Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.exe
$binaries += Get-Item src-tauri\target\release\wraith-mcp-bridge.exe -ErrorAction SilentlyContinue
foreach ($binary in $binaries) {
Write-Host "Signing: $($binary.FullName)"
java -jar jsign.jar --storetype AZUREKEYVAULT --keystore "${{ secrets.AZURE_KEY_VAULT_URL }}" --storepass $token --alias "${{ secrets.AZURE_CERT_NAME }}" --tsaurl http://timestamp.digicert.com --tsmode RFC3161 $binary.FullName
Write-Host "Signed: $($binary.Name)"
}
Remove-Item "$env:TEMP\aztoken.txt" -ErrorAction SilentlyContinue
- name: Upload to Gitea
shell: powershell
run: |
$ver = ("${{ github.ref_name }}" -replace '^v','')
$giteaUrl = "https://git.command.vigilcyber.com"
$headers = @{ Authorization = "token ${{ secrets.GIT_TOKEN }}" }
# Upload MCP bridge binary
$bridge = "src-tauri\target\release\wraith-mcp-bridge.exe"
if (Test-Path $bridge) {
Write-Host "Uploading: wraith-mcp-bridge.exe"
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/wraith-mcp-bridge.exe" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile $bridge
}
$installers = Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.exe
foreach ($file in $installers) {
$hash = (Get-FileHash $file.FullName -Algorithm SHA256).Hash.ToLower()
@{ version = $ver; filename = $file.Name; sha256 = $hash; platform = "windows"; architecture = "amd64"; released = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"); signed = $true } | ConvertTo-Json | Out-File version.json -Encoding utf8
Write-Host "Uploading: $($file.Name)"
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/$($file.Name)" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile $file.FullName
Write-Host "Uploading: version.json"
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/version.json" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile version.json
}
Write-Host "=== Upload complete ==="
- name: Generate and upload update.json for Tauri updater
shell: powershell
run: |
$ver = ("${{ github.ref_name }}" -replace '^v','')
$giteaUrl = "https://git.command.vigilcyber.com"
$headers = @{ Authorization = "token ${{ secrets.GIT_TOKEN }}" }
# Find the .sig file produced by Tauri signing
$sigFile = Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.nsis.zip.sig | Select-Object -First 1
$zipFile = Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.nsis.zip | Select-Object -First 1
if ($sigFile -and $zipFile) {
$signature = Get-Content $sigFile.FullName -Raw
$downloadUrl = "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/$($zipFile.Name)"
# Upload the .nsis.zip to packages
Write-Host "Uploading: $($zipFile.Name)"
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/$($zipFile.Name)" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile $zipFile.FullName
# Build update.json
$updateJson = @{
version = "v$ver"
notes = "Wraith Desktop v$ver"
pub_date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
platforms = @{
"windows-x86_64" = @{
signature = $signature.Trim()
url = $downloadUrl
}
}
} | ConvertTo-Json -Depth 4
$updateJson | Out-File update.json -Encoding utf8
Write-Host "update.json content:"
Get-Content update.json
# Upload to latest/ so the updater endpoint always points to the newest
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/latest/update.json" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile update.json
# Also upload to versioned path
Invoke-RestMethod -Uri "$giteaUrl/api/packages/vstockwell/generic/wraith/$ver/update.json" -Method PUT -Headers $headers -ContentType "application/octet-stream" -InFile update.json
Write-Host "=== Update manifest uploaded ==="
} else {
Write-Host "WARNING: No .sig file found — update signing may have failed"
Write-Host "Sig files found:"
Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.sig
}
- name: Create Release and attach installers
shell: powershell
run: |
$ver = ("${{ github.ref_name }}" -replace '^v','')
$giteaUrl = "https://git.command.vigilcyber.com"
$headers = @{ Authorization = "token ${{ secrets.GIT_TOKEN }}"; "Content-Type" = "application/json" }
$body = @{ tag_name = "v$ver"; name = "Wraith v$ver"; body = "Wraith Desktop v$ver - Tauri v2 / Rust build." } | ConvertTo-Json
$release = Invoke-RestMethod -Uri "$giteaUrl/api/v1/repos/vstockwell/wraith/releases" -Method POST -Headers $headers -Body $body
$releaseId = $release.id
Write-Host "Release v$ver created (id: $releaseId)"
$uploadHeaders = @{ Authorization = "token ${{ secrets.GIT_TOKEN }}" }
# Attach installer(s)
$installers = Get-ChildItem -Recurse src-tauri\target\release\bundle\nsis\*.exe
foreach ($file in $installers) {
Write-Host "Attaching $($file.Name) to release..."
Invoke-RestMethod -Uri "$giteaUrl/api/v1/repos/vstockwell/wraith/releases/$releaseId/assets?name=$($file.Name)" -Method POST -Headers $uploadHeaders -ContentType "application/octet-stream" -InFile $file.FullName
Write-Host "Attached: $($file.Name)"
}
# Attach MCP bridge binary
$bridge = "src-tauri\target\release\wraith-mcp-bridge.exe"
if (Test-Path $bridge) {
Write-Host "Attaching wraith-mcp-bridge.exe to release..."
Invoke-RestMethod -Uri "$giteaUrl/api/v1/repos/vstockwell/wraith/releases/$releaseId/assets?name=wraith-mcp-bridge.exe" -Method POST -Headers $uploadHeaders -ContentType "application/octet-stream" -InFile $bridge
Write-Host "Attached: wraith-mcp-bridge.exe"
}