vibetunnel/scripts/sign-and-notarize.sh
2025-06-15 23:14:29 +02:00

350 lines
No EOL
11 KiB
Bash
Executable file

#!/bin/bash
# sign-and-notarize.sh - Comprehensive code signing and notarization script for VibeTunnel
#
# This script handles the full process of:
# 1. Code signing with hardened runtime
# 2. Notarization with Apple
# 3. Stapling the notarization ticket
# 4. Creating distributable ZIP archives
set -euo pipefail
# Get script directory and ensure we're in the right location
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
APP_DIR="$(cd "$SCRIPT_DIR/.." &> /dev/null && pwd)"
cd "$APP_DIR" || { echo "Error: Failed to change directory to $APP_DIR"; exit 1; }
# Initialize variables with defaults
BUNDLE_DIR="build/Build/Products/Release/VibeTunnel.app"
APP_BUNDLE_PATH="$APP_DIR/$BUNDLE_DIR"
ZIP_PATH="$APP_DIR/build/VibeTunnel-notarize.zip"
FINAL_ZIP_PATH="$APP_DIR/build/VibeTunnel-notarized.zip"
MAX_RETRIES=3
RETRY_DELAY=30
TIMEOUT_MINUTES=30
# Operation flags - what parts of the process to run
DO_SIGNING=true
DO_NOTARIZATION=false
CREATE_ZIP=true
SKIP_STAPLE=false
VERBOSE=false
# Log helper function with timestamp
log() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1"
}
# Error logging
error() {
log "❌ ERROR: $1"
return 1
}
# Success logging
success() {
log "$1"
}
# Print usage information
print_usage() {
echo "Sign and Notarize Script for VibeTunnel Mac App"
echo ""
echo "Usage: $0 [options]"
echo ""
echo "Authentication Options (required for notarization):"
echo " --api-key-p8 KEY App Store Connect API key content (.p8)"
echo " --api-key-id ID App Store Connect API Key ID"
echo " --api-key-issuer ID App Store Connect API Key Issuer ID"
echo ""
echo "Process Control Options:"
echo " --sign-only Only perform code signing, skip notarization"
echo " --notarize-only Skip signing and only perform notarization"
echo " --sign-and-notarize Perform both signing and notarization (default if credentials provided)"
echo ""
echo "General Options:"
echo " --app-path PATH Path to the app bundle (default: $BUNDLE_DIR)"
echo " --identity ID Developer ID certificate to use for signing"
echo " --skip-staple Skip stapling the notarization ticket to the app"
echo " --no-zip Skip creating distributable ZIP archive"
echo " --timeout MINUTES Notarization timeout in minutes (default: 30)"
echo " --verbose Enable verbose output"
echo " --help Show this help message"
}
# Function to read credentials from environment and arguments
read_credentials() {
# Initialize with existing environment variables
local api_key_p8="${APP_STORE_CONNECT_API_KEY_P8:-}"
local api_key_id="${APP_STORE_CONNECT_KEY_ID:-}"
local api_key_issuer="${APP_STORE_CONNECT_ISSUER_ID:-}"
local sign_identity="${SIGN_IDENTITY:-Developer ID Application}"
# Save original arguments for explicit flag detection
local original_args=("$@")
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
# Authentication options
--api-key-p8)
api_key_p8="$2"
shift 2
;;
--api-key-id)
api_key_id="$2"
shift 2
;;
--api-key-issuer)
api_key_issuer="$2"
shift 2
;;
--identity)
sign_identity="$2"
shift 2
;;
# Process control options
--sign-only)
DO_SIGNING=true
DO_NOTARIZATION=false
shift
;;
--notarize-only)
DO_SIGNING=false
DO_NOTARIZATION=true
shift
;;
--sign-and-notarize)
DO_SIGNING=true
DO_NOTARIZATION=true
shift
;;
# General options
--app-path)
APP_BUNDLE_PATH="$2"
BUNDLE_DIR="$(basename "$APP_BUNDLE_PATH")"
shift 2
;;
--skip-staple)
SKIP_STAPLE=true
shift
;;
--no-zip)
CREATE_ZIP=false
shift
;;
--timeout)
TIMEOUT_MINUTES="$2"
shift 2
;;
--verbose)
VERBOSE=true
shift
;;
--help)
print_usage
exit 0
;;
*)
shift
;;
esac
done
# Export as environment variables
export APP_STORE_CONNECT_API_KEY_P8="$api_key_p8"
export APP_STORE_CONNECT_KEY_ID="$api_key_id"
export APP_STORE_CONNECT_ISSUER_ID="$api_key_issuer"
export SIGN_IDENTITY="$sign_identity"
# If notarization credentials are available and no explicit flags were set, enable notarization
if [ -n "$api_key_p8" ] && [ -n "$api_key_id" ] && [ -n "$api_key_issuer" ]; then
# Only auto-enable notarization if no explicit process control flag was provided
local explicit_flag_provided=false
for arg in "${original_args[@]}"; do
case "$arg" in
--sign-only|--notarize-only|--sign-and-notarize)
explicit_flag_provided=true
break
;;
esac
done
if [ "$explicit_flag_provided" = false ] && [ "$DO_NOTARIZATION" = false ] && [ "$DO_SIGNING" = true ]; then
DO_NOTARIZATION=true
log "Notarization credentials detected. Will perform both signing and notarization."
fi
fi
}
# Retry function for operations that might fail
retry_operation() {
local cmd="$1"
local desc="$2"
local attempt=1
local result
while [ $attempt -le $MAX_RETRIES ]; do
log "Attempt $attempt/$MAX_RETRIES: $desc"
if result=$(eval "$cmd" 2>&1); then
echo "$result"
return 0
else
local exit_code=$?
log "Attempt $attempt failed (exit code: $exit_code)"
if [ "$VERBOSE" = "true" ]; then
log "Command output: $result"
fi
if [ $attempt -lt $MAX_RETRIES ]; then
log "Retrying in $RETRY_DELAY seconds..."
sleep $RETRY_DELAY
fi
fi
attempt=$((attempt + 1))
done
error "Failed after $MAX_RETRIES attempts: $desc"
echo "$result"
return 1
}
# Function to perform code signing
perform_signing() {
log "Starting code signing process for VibeTunnel..."
# Check if the app bundle exists
if [ ! -d "$APP_BUNDLE_PATH" ]; then
error "App bundle not found at $APP_BUNDLE_PATH"
log "Please build the app first by running ./scripts/build.sh"
exit 1
fi
log "Found app bundle at $APP_BUNDLE_PATH"
# Call the codesign script
if ! "$SCRIPT_DIR/codesign-app.sh" "$APP_BUNDLE_PATH" "$SIGN_IDENTITY"; then
error "Code signing failed"
exit 1
fi
success "Code signing completed successfully!"
}
# Function to perform app notarization
perform_notarization() {
log "Starting notarization process for VibeTunnel..."
# Check for authentication requirements
MISSING_VARS=()
[ -z "${APP_STORE_CONNECT_API_KEY_P8:-}" ] && MISSING_VARS+=("APP_STORE_CONNECT_API_KEY_P8")
[ -z "${APP_STORE_CONNECT_KEY_ID:-}" ] && MISSING_VARS+=("APP_STORE_CONNECT_KEY_ID")
[ -z "${APP_STORE_CONNECT_ISSUER_ID:-}" ] && MISSING_VARS+=("APP_STORE_CONNECT_ISSUER_ID")
if [ ${#MISSING_VARS[@]} -gt 0 ]; then
error "Missing required variables for notarization: ${MISSING_VARS[*]}"
log "Please provide --api-key-p8, --api-key-id, and --api-key-issuer options"
log "or set the corresponding environment variables."
exit 1
fi
# Ensure app is signed if needed
if [ "$DO_SIGNING" = true ] || ! codesign --verify --verbose=1 "$APP_BUNDLE_PATH" &>/dev/null; then
log "Signing needs to be performed before notarization..."
perform_signing
else
log "App already properly signed, skipping signing step"
fi
# Call the notarization script
if ! "$SCRIPT_DIR/notarize-app.sh" "$APP_BUNDLE_PATH"; then
error "Notarization failed"
exit 1
fi
success "Notarization completed successfully!"
# Create distributable ZIP archive if needed
if [ "$CREATE_ZIP" = true ]; then
log "Creating distributable ZIP archive..."
rm -f "$FINAL_ZIP_PATH" # Remove existing zip if any
mkdir -p "$(dirname "$FINAL_ZIP_PATH")"
if ! ditto -c -k --keepParent "$APP_BUNDLE_PATH" "$FINAL_ZIP_PATH"; then
error "Failed to create ZIP archive"
else
success "Distributable ZIP archive created: $FINAL_ZIP_PATH"
# Calculate file size and hash for verification
ZIP_SIZE=$(du -h "$FINAL_ZIP_PATH" | cut -f1)
ZIP_SHA=$(shasum -a 256 "$FINAL_ZIP_PATH" | cut -d' ' -f1)
log "ZIP archive size: $ZIP_SIZE"
log "ZIP SHA-256 hash: $ZIP_SHA"
fi
fi
}
# Main execution starts here
log "Starting sign and notarize script for VibeTunnel..."
# Read credentials from all possible sources
read_credentials "$@"
# Check if the app bundle exists
if [ ! -d "$APP_BUNDLE_PATH" ]; then
error "App bundle not found at $APP_BUNDLE_PATH"
log "Please build the app first by running ./scripts/build.sh"
exit 1
fi
log "Found app bundle at $APP_BUNDLE_PATH"
# Check if we should do code signing
if [ "$DO_SIGNING" = true ]; then
perform_signing
else
log "Skipping code signing as requested"
fi
# Check if we should do notarization
if [ "$DO_NOTARIZATION" = true ]; then
perform_notarization
else
log "Skipping notarization as requested"
# Create a simple ZIP file if signing only and zip creation is requested
if [ "$DO_SIGNING" = true ] && [ "$CREATE_ZIP" = true ]; then
log "Creating distributable ZIP archive after signing..."
mkdir -p "$(dirname "$FINAL_ZIP_PATH")"
if ! ditto -c -k --keepParent "$APP_BUNDLE_PATH" "$FINAL_ZIP_PATH"; then
error "Failed to create ZIP archive"
else
success "Distributable ZIP archive created: $FINAL_ZIP_PATH"
ZIP_SIZE=$(du -h "$FINAL_ZIP_PATH" | cut -f1)
ZIP_SHA=$(shasum -a 256 "$FINAL_ZIP_PATH" | cut -d' ' -f1)
log "ZIP archive size: $ZIP_SIZE"
log "ZIP SHA-256 hash: $ZIP_SHA"
fi
fi
fi
# Print final status summary
log ""
log "Operation summary:"
log "✅ App bundle: $APP_BUNDLE_PATH"
if [ "$DO_SIGNING" = true ]; then
log "✅ Code signing: Completed"
fi
if [ "$DO_NOTARIZATION" = true ]; then
log "✅ Notarization: Completed"
if [ "$SKIP_STAPLE" = false ]; then
log "✅ Stapling: Completed (users can run without security warnings)"
else
log "⚠️ Stapling: Skipped"
fi
fi
if [ "$CREATE_ZIP" = true ] && [ -f "$FINAL_ZIP_PATH" ]; then
log "✅ Distributable ZIP archive: $FINAL_ZIP_PATH"
fi
success "Script completed successfully!"