mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-22 14:06:02 +00:00
- Add state tracking and resume capability to release process - Create release-state.sh for tracking 9 major release steps - Add --resume and --status flags to release.sh - Fix private key format handling for sign_update tool - Create clean key file (sparkle_ed_private_key) automatically - Handle missing custom Node.js builds gracefully - Add DerivedData app location fallback - Create comprehensive release-checklist.sh script - Update release documentation with critical learnings - Enhance Stats.store documentation with setup instructions - Add troubleshooting for 'Application not found' error - Document fallback options for direct GitHub URLs - Update all scripts to handle clean key file format These improvements make the release process more reliable and resilient to interruptions, with better error handling throughout.
218 lines
No EOL
7 KiB
Bash
Executable file
218 lines
No EOL
7 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Validate Sparkle signatures are correct before release
|
|
#
|
|
# This script helps prevent the "improperly signed" error by verifying
|
|
# that signatures in appcast files match the actual DMG files.
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for output
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
# Use the clean key file without comments for sign_update
|
|
SPARKLE_PRIVATE_KEY_PATH="${SPARKLE_PRIVATE_KEY_PATH:-private/sparkle_ed_private_key}"
|
|
# Fallback to commented key file if clean one doesn't exist
|
|
if [ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ] && [ -f "private/sparkle_private_key" ]; then
|
|
# Extract just the key from the commented file
|
|
KEY_LINE=$(grep -E '^[A-Za-z0-9+/]+=*$' "private/sparkle_private_key" | head -1)
|
|
if [ -n "$KEY_LINE" ]; then
|
|
echo "$KEY_LINE" > "private/sparkle_ed_private_key"
|
|
SPARKLE_PRIVATE_KEY_PATH="private/sparkle_ed_private_key"
|
|
else
|
|
SPARKLE_PRIVATE_KEY_PATH="private/sparkle_private_key"
|
|
fi
|
|
fi
|
|
EXPECTED_PUBLIC_KEY="AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI="
|
|
|
|
# Function to print colored output
|
|
print_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
# Check if sign_update is available
|
|
if ! command -v sign_update >/dev/null 2>&1; then
|
|
print_error "sign_update not found in PATH"
|
|
echo "Please install Sparkle tools or add them to PATH"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify private key exists
|
|
if [ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ]; then
|
|
print_error "Sparkle private key not found at: $SPARKLE_PRIVATE_KEY_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
print_info "Using private key at: $SPARKLE_PRIVATE_KEY_PATH"
|
|
print_info "Expected public key: $EXPECTED_PUBLIC_KEY"
|
|
echo
|
|
|
|
# Function to validate appcast file
|
|
validate_appcast() {
|
|
local appcast_file=$1
|
|
local channel_name=$2
|
|
|
|
if [ ! -f "$appcast_file" ]; then
|
|
print_warning "$appcast_file not found, skipping"
|
|
return
|
|
fi
|
|
|
|
print_info "Validating $channel_name channel ($appcast_file)..."
|
|
|
|
# Extract all DMG URLs and signatures from appcast
|
|
local entries=$(grep -A 5 "<enclosure" "$appcast_file" | grep -E "(url=|sparkle:edSignature=)" | paste -d' ' - - | sed 's/.*url="\([^"]*\)".*/\1/' | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
|
|
|
|
local found_issues=0
|
|
|
|
while IFS=' ' read -r dmg_url signature; do
|
|
[ -z "$dmg_url" ] && continue
|
|
|
|
# Extract filename from URL
|
|
local dmg_filename=$(basename "$dmg_url")
|
|
|
|
# Skip if not a DMG URL
|
|
if [[ ! "$dmg_filename" =~ \.dmg$ ]]; then
|
|
continue
|
|
fi
|
|
|
|
print_info "Checking $dmg_filename..."
|
|
|
|
# Download DMG if not already present
|
|
if [ ! -f "/tmp/$dmg_filename" ]; then
|
|
print_info " Downloading from GitHub..."
|
|
if ! curl -sL "$dmg_url" -o "/tmp/$dmg_filename" 2>/dev/null; then
|
|
print_error " Failed to download $dmg_filename"
|
|
((found_issues++))
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Generate signature with correct private key
|
|
print_info " Generating signature with file-based key..."
|
|
local correct_signature=$(sign_update -f "$SPARKLE_PRIVATE_KEY_PATH" "/tmp/$dmg_filename" 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
|
|
|
|
if [ -z "$correct_signature" ]; then
|
|
print_error " Failed to generate signature for $dmg_filename"
|
|
((found_issues++))
|
|
continue
|
|
fi
|
|
|
|
# Compare signatures
|
|
if [ "$signature" = "$correct_signature" ]; then
|
|
print_success " ✓ Signature is correct"
|
|
else
|
|
print_error " ✗ Signature mismatch!"
|
|
echo " Appcast has: $signature"
|
|
echo " Should be: $correct_signature"
|
|
((found_issues++))
|
|
fi
|
|
|
|
# Clean up
|
|
rm -f "/tmp/$dmg_filename"
|
|
done < <(xmllint --format "$appcast_file" 2>/dev/null | grep -A 1 'url=".*\.dmg"' | grep -E "(url=|sparkle:edSignature=)" | paste -d' ' - - | sed 's/.*url="\([^"]*\)".*/\1 /' | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
|
|
|
|
if [ $found_issues -eq 0 ]; then
|
|
print_success "✓ All signatures in $channel_name channel are correct!"
|
|
else
|
|
print_error "✗ Found $found_issues signature issues in $channel_name channel"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to test sign_update with different methods
|
|
test_signing_methods() {
|
|
print_info "Testing different signing methods..."
|
|
|
|
# Create a test file
|
|
echo "test" > /tmp/sparkle_test.txt
|
|
|
|
# Test with file-based key (correct)
|
|
print_info "Testing with file-based key (-f flag)..."
|
|
local file_sig=$(sign_update -f "$SPARKLE_PRIVATE_KEY_PATH" /tmp/sparkle_test.txt 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
|
|
|
|
# Test without -f flag (may use keychain)
|
|
print_info "Testing without -f flag (may use keychain)..."
|
|
local keychain_sig=$(sign_update /tmp/sparkle_test.txt 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
|
|
|
|
rm -f /tmp/sparkle_test.txt
|
|
|
|
if [ -n "$file_sig" ]; then
|
|
print_success "File-based key produces signature: ${file_sig:0:20}..."
|
|
else
|
|
print_error "Failed to generate signature with file-based key"
|
|
fi
|
|
|
|
if [ -n "$keychain_sig" ]; then
|
|
print_warning "Keychain key produces signature: ${keychain_sig:0:20}..."
|
|
|
|
if [ "$file_sig" != "$keychain_sig" ]; then
|
|
print_error "⚠️ WARNING: Keychain has a DIFFERENT key than the file!"
|
|
print_error "⚠️ ALWAYS use the -f flag to ensure correct signatures!"
|
|
else
|
|
print_info "Keychain and file keys match (this is good)"
|
|
fi
|
|
else
|
|
print_info "No keychain key found (this is fine)"
|
|
fi
|
|
|
|
echo
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
echo "=== Sparkle Signature Validation Tool ==="
|
|
echo
|
|
|
|
# Test signing methods
|
|
test_signing_methods
|
|
|
|
# Validate appcast files
|
|
local exit_code=0
|
|
|
|
if ! validate_appcast "../appcast.xml" "Stable"; then
|
|
exit_code=1
|
|
fi
|
|
|
|
echo
|
|
|
|
if ! validate_appcast "../appcast-prerelease.xml" "Pre-release"; then
|
|
exit_code=1
|
|
fi
|
|
|
|
echo
|
|
echo "=== Summary ==="
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
print_success "All signatures are valid! ✅"
|
|
print_info "Your appcast files are correctly signed."
|
|
else
|
|
print_error "Signature validation failed! ❌"
|
|
print_warning "To fix:"
|
|
echo "1. Download the DMG files from GitHub"
|
|
echo "2. Generate correct signatures with:"
|
|
echo " sign_update -f $SPARKLE_PRIVATE_KEY_PATH [dmg-file]"
|
|
echo "3. Update the appcast XML files with the correct signatures"
|
|
echo "4. Commit and push the updated appcast files"
|
|
fi
|
|
|
|
exit $exit_code
|
|
}
|
|
|
|
# Run main function
|
|
main "$@" |