fix: correct checksum file path in validation (cd .. instead of cd ..… #19
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' # Trigger on version tags like v1.0.0 | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., v1.0.0)' | |
| required: true | |
| type: string | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # Security scanning before building | |
| # Note: Duplicates security.yml checks to gate releases | |
| security-check: | |
| runs-on: ubicloud-standard-8 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: TruffleHog Secret Scan | |
| uses: trufflesecurity/trufflehog@main | |
| with: | |
| path: ./ | |
| base: ${{ github.event.repository.default_branch }} | |
| head: HEAD | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Audit npm dependencies | |
| run: npm audit --audit-level=moderate | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo-audit | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/bin/cargo-audit | |
| key: ${{ runner.os }}-cargo-audit-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Install cargo-audit | |
| run: cargo install cargo-audit --locked | |
| - name: Audit Rust dependencies | |
| run: cd src-tauri && cargo audit | |
| release-macos: | |
| needs: security-check | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-apple-darwin,x86_64-apple-darwin | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Import Apple Certificate | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| # Create temporary keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| 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 | |
| echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 | |
| security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| # Set the keychain as default search list | |
| security list-keychain -d user -s $KEYCHAIN_PATH login.keychain | |
| # Allow codesign to access keychain | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # Clean up certificate file | |
| rm certificate.p12 | |
| echo "✅ Certificate imported successfully" | |
| # Verify certificate is available | |
| echo "📋 Available signing identities:" | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| - name: Build Tauri app (Universal Binary) | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| run: npm run tauri build -- --target universal-apple-darwin | |
| - name: Notarize macOS app | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| run: | | |
| # Find the DMG file | |
| DMG_PATH=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -name "*.dmg" | head -n 1) | |
| if [ -z "$DMG_PATH" ]; then | |
| echo "❌ Error: DMG file not found in expected location" | |
| echo "Expected path: src-tauri/target/universal-apple-darwin/release/bundle/dmg/" | |
| echo "Available files:" | |
| find src-tauri/target/universal-apple-darwin/release/bundle -type f || true | |
| exit 1 | |
| fi | |
| echo "📦 Found DMG: $DMG_PATH" | |
| # Verify DMG is valid before notarization | |
| echo "🔍 Validating DMG integrity..." | |
| if ! hdiutil verify "$DMG_PATH"; then | |
| echo "❌ Error: DMG file is corrupted or invalid" | |
| exit 1 | |
| fi | |
| echo "✅ DMG integrity verified" | |
| # Verify code signing before notarization | |
| echo "🔍 Verifying code signature..." | |
| if ! codesign -dv --verbose=4 "$DMG_PATH" 2>&1; then | |
| echo "❌ Error: DMG is not properly code signed" | |
| exit 1 | |
| fi | |
| echo "✅ Code signature verified" | |
| echo "🔐 Submitting for notarization..." | |
| # Submit for notarization with retry logic | |
| SUBMISSION_OUTPUT=$(mktemp) | |
| MAX_RETRIES=3 | |
| RETRY_COUNT=0 | |
| SUCCESS=false | |
| while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do | |
| if [ $RETRY_COUNT -gt 0 ]; then | |
| echo "⏳ Retry attempt $RETRY_COUNT of $((MAX_RETRIES - 1))..." | |
| sleep 30 | |
| fi | |
| if xcrun notarytool submit "$DMG_PATH" \ | |
| --apple-id "$APPLE_ID" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --wait \ | |
| --timeout 30m 2>&1 | tee "$SUBMISSION_OUTPUT"; then | |
| SUCCESS=true | |
| break | |
| fi | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| echo "⚠️ Notarization attempt $RETRY_COUNT failed" | |
| done | |
| if [ "$SUCCESS" = false ]; then | |
| echo "❌ Notarization failed after $MAX_RETRIES attempts" | |
| echo "Final submission output:" | |
| cat "$SUBMISSION_OUTPUT" | |
| # Try to extract submission ID for logs | |
| SUBMISSION_ID=$(grep -oE '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}' "$SUBMISSION_OUTPUT" | head -n 1 || true) | |
| if [ -n "$SUBMISSION_ID" ]; then | |
| echo "📋 Fetching notarization logs for submission: $SUBMISSION_ID" | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --password "$APPLE_PASSWORD" || true | |
| fi | |
| rm -f "$SUBMISSION_OUTPUT" | |
| exit 1 | |
| fi | |
| rm -f "$SUBMISSION_OUTPUT" | |
| echo "✅ Notarization complete!" | |
| # Staple the notarization ticket | |
| echo "📎 Stapling notarization ticket..." | |
| if ! xcrun stapler staple "$DMG_PATH"; then | |
| echo "❌ Error: Failed to staple notarization ticket" | |
| echo "This may indicate the notarization didn't complete successfully" | |
| exit 1 | |
| fi | |
| echo "✅ Stapling complete!" | |
| # Verify notarization with Gatekeeper | |
| echo "🔍 Verifying notarization with Gatekeeper..." | |
| if ! spctl -a -vvv -t install "$DMG_PATH" 2>&1; then | |
| echo "❌ Error: Gatekeeper verification failed" | |
| echo "The app may not open on user systems" | |
| exit 1 | |
| fi | |
| echo "✅ Gatekeeper verification passed!" | |
| # Final validation | |
| echo "🔍 Final validation checks..." | |
| echo "Staple status:" | |
| xcrun stapler validate "$DMG_PATH" || { | |
| echo "⚠️ Warning: Staple validation failed" | |
| echo "The app is notarized but stapling may need to be checked" | |
| } | |
| echo "✅ All notarization checks passed!" | |
| - name: Generate checksums | |
| run: | | |
| cd src-tauri/target/universal-apple-darwin/release/bundle/dmg | |
| # Verify DMG exists before generating checksums | |
| if [ ! -f *.dmg ]; then | |
| echo "❌ Error: No DMG file found for checksum generation" | |
| exit 1 | |
| fi | |
| shasum -a 256 *.dmg > checksums-macos.txt | |
| # Verify checksum file was created and is not empty | |
| if [ ! -s checksums-macos.txt ]; then | |
| echo "❌ Error: Checksum file is empty or was not created" | |
| exit 1 | |
| fi | |
| echo "✅ Checksums generated:" | |
| cat checksums-macos.txt | |
| - name: Validate artifacts before upload | |
| run: | | |
| DMG_PATH=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -name "*.dmg" | head -n 1) | |
| echo "🔍 Final artifact validation..." | |
| # Verify file exists and has content | |
| if [ ! -f "$DMG_PATH" ]; then | |
| echo "❌ Error: DMG file not found" | |
| exit 1 | |
| fi | |
| FILE_SIZE=$(stat -f%z "$DMG_PATH" 2>/dev/null || stat -c%s "$DMG_PATH" 2>/dev/null) | |
| if [ "$FILE_SIZE" -lt 1048576 ]; then | |
| echo "❌ Error: DMG file is suspiciously small ($FILE_SIZE bytes)" | |
| exit 1 | |
| fi | |
| echo "✅ DMG size: $(echo "$FILE_SIZE" | awk '{printf "%.2f MB", $1/1024/1024}')" | |
| # Verify notarization is still valid | |
| echo "🔍 Re-verifying notarization before upload..." | |
| if ! xcrun stapler validate "$DMG_PATH" >/dev/null 2>&1; then | |
| echo "⚠️ Warning: Staple validation check failed" | |
| fi | |
| echo "✅ All artifact validations passed!" | |
| - name: Upload macOS artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-dmg | |
| path: | | |
| src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg | |
| src-tauri/target/universal-apple-darwin/release/bundle/dmg/checksums-macos.txt | |
| - name: Cleanup keychain | |
| if: always() | |
| run: | | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true | |
| release-linux: | |
| needs: security-check | |
| runs-on: ubicloud-standard-8 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgtk-3-dev \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Tauri app | |
| run: npm run tauri build | |
| - name: Generate checksums | |
| run: | | |
| cd src-tauri/target/release/bundle/deb | |
| # Verify DEB files exist | |
| if ! ls *.deb 1> /dev/null 2>&1; then | |
| echo "❌ Error: No DEB files found for checksum generation" | |
| exit 1 | |
| fi | |
| echo "📦 Generating checksums..." | |
| shasum -a 256 *.deb > ../checksums-linux.txt | |
| # Verify checksum file was created and is not empty | |
| if [ ! -s ../checksums-linux.txt ]; then | |
| echo "❌ Error: Checksum file is empty or was not created" | |
| exit 1 | |
| fi | |
| echo "✅ Checksums generated:" | |
| cat ../checksums-linux.txt | |
| - name: Validate Linux artifacts before upload | |
| run: | | |
| echo "🔍 Validating Linux artifacts..." | |
| # Debug: Show what find actually returns | |
| echo "📋 Searching for DEB files..." | |
| find src-tauri/target/release/bundle/deb -type f -name "*.deb" -print || { | |
| echo "❌ Error running find command" | |
| echo "Contents of bundle directory:" | |
| ls -laR src-tauri/target/release/bundle/ || true | |
| exit 1 | |
| } | |
| # Check DEB files using a more robust approach | |
| cd src-tauri/target/release/bundle/deb | |
| shopt -s nullglob | |
| DEB_FILES=(*.deb) | |
| if [ ${#DEB_FILES[@]} -eq 0 ]; then | |
| echo "❌ Error: No DEB files found" | |
| echo "Contents of deb directory:" | |
| ls -laR . || true | |
| exit 1 | |
| fi | |
| echo "📦 Found ${#DEB_FILES[@]} DEB file(s)" | |
| for deb in "${DEB_FILES[@]}"; do | |
| FILE_SIZE=$(stat -c%s "$deb") | |
| SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $FILE_SIZE/1048576}") | |
| echo "✅ $deb: ${SIZE_MB} MB" | |
| if [ "$FILE_SIZE" -lt 1048576 ]; then | |
| echo "❌ Error: DEB file is suspiciously small ($FILE_SIZE bytes)" | |
| exit 1 | |
| fi | |
| done | |
| # Check checksum file (go back to bundle directory, not release) | |
| cd .. | |
| CHECKSUM_FILE="checksums-linux.txt" | |
| if [ ! -f "$CHECKSUM_FILE" ]; then | |
| echo "❌ Error: Checksum file not found at: $(pwd)/$CHECKSUM_FILE" | |
| echo "Contents of current directory:" | |
| ls -la . || true | |
| exit 1 | |
| fi | |
| CHECKSUM_SIZE=$(stat -c%s "$CHECKSUM_FILE") | |
| if [ "$CHECKSUM_SIZE" -eq 0 ]; then | |
| echo "❌ Error: Checksum file is empty" | |
| exit 1 | |
| fi | |
| echo "✅ Checksum file: $CHECKSUM_SIZE bytes" | |
| echo "✅ All Linux artifact validations passed!" | |
| - name: Upload Linux artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-packages | |
| path: | | |
| src-tauri/target/release/bundle/deb/*.deb | |
| src-tauri/target/release/bundle/checksums-linux.txt | |
| release-windows: | |
| needs: security-check | |
| runs-on: windows-2022-16-cores | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Tauri app | |
| run: npm run tauri build | |
| - name: Generate checksums | |
| shell: pwsh | |
| run: | | |
| cd src-tauri/target/release/bundle/msi | |
| # Verify MSI files exist | |
| $msiFiles = Get-ChildItem -Filter *.msi | |
| if ($msiFiles.Count -eq 0) { | |
| Write-Host "❌ Error: No MSI files found for checksum generation" | |
| exit 1 | |
| } | |
| Write-Host "📦 Found $($msiFiles.Count) MSI file(s)" | |
| # Generate checksums | |
| $msiFiles | ForEach-Object { | |
| $hash = Get-FileHash $_.FullName -Algorithm SHA256 | |
| "$($hash.Hash) $($_.Name)" | |
| } | Out-File -FilePath ..\checksums-windows.txt -Encoding UTF8 | |
| # Verify checksum file was created and is not empty | |
| $checksumFile = "..\checksums-windows.txt" | |
| if (-not (Test-Path $checksumFile)) { | |
| Write-Host "❌ Error: Checksum file was not created" | |
| exit 1 | |
| } | |
| $fileSize = (Get-Item $checksumFile).Length | |
| if ($fileSize -eq 0) { | |
| Write-Host "❌ Error: Checksum file is empty" | |
| exit 1 | |
| } | |
| Write-Host "✅ Checksums generated ($fileSize bytes):" | |
| Get-Content $checksumFile | |
| - name: Validate Windows artifacts before upload | |
| shell: pwsh | |
| run: | | |
| $msiPath = "src-tauri/target/release/bundle/msi" | |
| $checksumPath = "src-tauri/target/release/bundle/checksums-windows.txt" | |
| Write-Host "🔍 Validating Windows artifacts..." | |
| # Check MSI files | |
| $msiFiles = Get-ChildItem -Path $msiPath -Filter *.msi | |
| if ($msiFiles.Count -eq 0) { | |
| Write-Host "❌ Error: No MSI files found" | |
| exit 1 | |
| } | |
| foreach ($msi in $msiFiles) { | |
| $sizeMB = [math]::Round($msi.Length / 1MB, 2) | |
| Write-Host "✅ $($msi.Name): $sizeMB MB" | |
| if ($msi.Length -lt 1MB) { | |
| Write-Host "❌ Error: MSI file is suspiciously small" | |
| exit 1 | |
| } | |
| } | |
| # Check checksum file | |
| if (-not (Test-Path $checksumPath)) { | |
| Write-Host "❌ Error: Checksum file not found" | |
| exit 1 | |
| } | |
| $checksumSize = (Get-Item $checksumPath).Length | |
| if ($checksumSize -eq 0) { | |
| Write-Host "❌ Error: Checksum file is empty" | |
| exit 1 | |
| } | |
| Write-Host "✅ Checksum file: $checksumSize bytes" | |
| Write-Host "✅ All Windows artifact validations passed!" | |
| - name: Upload Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-installers | |
| path: | | |
| src-tauri/target/release/bundle/msi/*.msi | |
| src-tauri/target/release/bundle/checksums-windows.txt | |
| create-release: | |
| needs: [release-macos, release-linux, release-windows] | |
| runs-on: ubicloud-standard-8 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Display structure of downloaded files | |
| run: ls -R artifacts | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| artifacts/macos-dmg/*.dmg | |
| artifacts/macos-dmg/*.txt | |
| artifacts/linux-packages/deb/*.deb | |
| artifacts/linux-packages/*.txt | |
| artifacts/windows-installers/msi/*.msi | |
| artifacts/windows-installers/*.txt | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| body: | | |
| ## Installation | |
| ### macOS | |
| 1. Download the `.dmg` file | |
| 2. Open the DMG and drag OpenObserve Kide to Applications | |
| 3. Right-click and select "Open" on first launch (required for notarized apps) | |
| 4. Verify signature: `codesign -dv --verbose=4 "/Applications/OpenObserve Kide.app"` | |
| ### Linux | |
| - **Debian/Ubuntu**: Download and install the `.deb` package | |
| ```bash | |
| sudo dpkg -i openobserve-kide_*.deb | |
| ``` | |
| ### Windows | |
| - Download and run the `.msi` installer | |
| ## Verification | |
| All releases include SHA-256 checksums for each platform. Verify your download: | |
| ```bash | |
| # macOS/Linux | |
| shasum -a 256 -c checksums-macos.txt | |
| shasum -a 256 -c checksums-linux.txt | |
| # Windows (PowerShell) | |
| Get-Content checksums-windows.txt | |
| Get-FileHash <downloaded-file> -Algorithm SHA256 | |
| ``` | |
| ## Security | |
| - macOS app is code-signed and notarized by Apple | |
| - All builds undergo security scanning (TruffleHog, npm audit, cargo audit) | |
| - All builds are reproducible from source | |
| - See [SECURITY.md](https://github.com/${{ github.repository }}/blob/main/SECURITY.md) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |