From d4765938a88da3c08f0677a85cfb1264eb910e39 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:22:02 +0000 Subject: [PATCH] publish GHAs (#5026) # Description of Changes --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .github/workflows/multiOSReleases.yml | 650 +++++++++++++++++-------- .github/workflows/push-docker-v2.yml | 244 ++++++++++ .github/workflows/releaseArtifacts.yml | 555 +++++++++++++++++---- docker/Dockerfile.unified-lite | 108 ++++ 4 files changed, 1250 insertions(+), 307 deletions(-) create mode 100644 .github/workflows/push-docker-v2.yml create mode 100644 docker/Dockerfile.unified-lite diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index 1e4a18f91..93d5ac144 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -1,4 +1,4 @@ -name: Test Installers Build +name: Multi-OS Tauri Releases on: workflow_dispatch: @@ -6,7 +6,23 @@ on: test_mode: description: "Run in test mode (skip release step)" required: false - default: "false" + default: "true" + type: choice + options: + - "true" + - "false" + platform: + description: "Platform to build (windows, macos, linux, or all)" + required: true + default: "all" + type: choice + options: + - all + - windows + - macos + - linux + push: + branches: [main, V2, V2-demo, V2-master] release: types: [created] @@ -14,41 +30,63 @@ permissions: contents: read jobs: - read_versions: + determine-matrix: runs-on: ubuntu-latest outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} version: ${{ steps.versionNumber.outputs.versionNumber }} - versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }} steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Set up JDK - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - distribution: 'temurin' - java-version: '21' + java-version: "21" + distribution: "temurin" + + - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + with: + gradle-version: 8.14 - # ✅ Get version from Gradle - name: Get version number id: versionNumber run: | + echo "Running gradlew printVersion..." + ./gradlew printVersion --quiet VERSION=$(./gradlew printVersion --quiet | tail -1) + echo "Extracted version: $VERSION" echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT - # ✅ Get Mac-specific version from Gradle - - name: Get version number mac - id: versionNumberMac + - name: Determine build matrix + id: set-matrix run: | - VERSION_MAC=$(./gradlew printMacVersion --quiet | tail -1) - echo "versionNumberMac=$VERSION_MAC" >> $GITHUB_OUTPUT + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + case "${{ github.event.inputs.platform }}" in + "windows") + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "macos") + echo 'matrix={"include":[{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "linux") + echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + *) + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + esac + else + # For push/release events, build all platforms + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + fi - build-portable: - needs: read_versions + build-jars: + needs: determine-matrix runs-on: ubuntu-latest strategy: matrix: @@ -64,10 +102,10 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK 21 - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: "21" distribution: "temurin" @@ -76,226 +114,401 @@ jobs: with: gradle-version: 8.14 - - name: Generate jar (Disable Security=${{ matrix.disable_security }}) - run: ./gradlew clean createExe + - name: Build JAR + run: ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube env: DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }} STIRLING_PDF_DESKTOP_UI: false - - name: Rename binaries + - name: Rename JAR run: | - mkdir ./binaries - mv ./build/launch4j/Stirling-PDF.exe ./binaries/win-Stirling-PDF-portable-Server${{ matrix.file_suffix }}.exe - mv ./app/core/build/libs/stirling-pdf-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar + echo "Version from determine-matrix: ${{ needs.determine-matrix.outputs.version }}" + echo "Looking for: app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar" + ls -la app/core/build/libs/ + mkdir -p ./jar-dist + cp app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar ./jar-dist/Stirling-PDF${{ matrix.file_suffix }}.jar - - name: Upload build artifacts + - name: Upload JAR artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: + name: jar${{ matrix.file_suffix }} + path: ./jar-dist/*.jar retention-days: 1 - if-no-files-found: error - name: stirling${{ matrix.file_suffix }}-binaries - path: | - ./binaries/* - sign_verify-portable: - needs: [build-portable, read_versions] - runs-on: ubuntu-latest + build: + needs: determine-matrix strategy: - matrix: - disable_security: [true, false] - include: - - disable_security: false - file_suffix: "with-login-" - - disable_security: true - file_suffix: "" + fail-fast: false + matrix: ${{ fromJson(needs.determine-matrix.outputs.matrix) }} + runs-on: ${{ matrix.platform }} + env: + SM_API_KEY: ${{ secrets.SM_API_KEY }} + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit - - name: Download build artifacts - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - name: stirling-${{ matrix.file_suffix }}binaries + node-version: 20 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json - - name: Display structure of downloaded files - run: ls -R - - - name: Upload signed artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable with: - retention-days: 1 - if-no-files-found: error - name: stirling-${{ matrix.file_suffix }}signed - path: | - ./* - !cosign.* + toolchain: stable + targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - build-installers: - needs: read_versions - strategy: - matrix: - include: - - os: windows-latest - platform: win- - - os: macos-latest - platform: mac- - # - os: ubuntu-latest - # platform: linux- - runs-on: ${{ matrix.os }} - permissions: - contents: write - steps: - - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 - with: - egress-policy: audit - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Set up JDK 21 - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: "21" distribution: "temurin" - - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - with: - gradle-version: 8.14 - - # Install Windows dependencies - - name: Install WiX Toolset - if: matrix.os == 'windows-latest' - run: | - curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe - .\wix.exe /install /quiet - - # Build installer - - name: Build Installer - run: ./gradlew build jpackage -x test --info - env: - DISABLE_ADDITIONAL_FEATURES: true - STIRLING_PDF_DESKTOP_UI: true - BROWSER_OPEN: true - - - name: Set up JDK (x86_64) - if: matrix.os == 'macos-latest' - run: | - curl -L -o jdk.tar.gz https://cdn.azul.com/zulu/bin/zulu17.56.15-ca-jdk17.0.14-macosx_x64.tar.gz - mkdir -p zulu17 - tar -xzf jdk.tar.gz -C zulu17 --strip-components=1 - echo "JAVA_HOME=$PWD/zulu17" >> $GITHUB_ENV - echo "$PWD/zulu17/bin" >> $GITHUB_PATH - - - name: Verify JDK architecture - if: matrix.os == 'macos-latest' - run: file $JAVA_HOME/bin/java - - - name: Build project and run jpackage (x86_64) - if: matrix.os == 'macos-latest' - run: arch -x86_64 ./gradlew jpackageMacX64 - - # Rename and collect artifacts based on OS - - name: Prepare artifacts - id: prepare + - name: Build Java backend with JLink + working-directory: ./ shell: bash run: | - ls -lah ./build/jpackage/ - mkdir ./binaries - if [ "${{ matrix.os }}" = "windows-latest" ]; then - mv "./build/jpackage/Stirling PDF-${{ needs.read_versions.outputs.version }}.exe" "./binaries/Stirling-PDF-win-installer.exe" - elif [ "${{ matrix.os }}" = "macos-latest" ]; then - mv "./build/jpackage/Stirling PDF-${{ needs.read_versions.outputs.versionMac }}.dmg" "./binaries/Stirling-PDF-mac-installer.dmg" - mv "./build/jpackage/x86_64/Stirling PDF (x86_64)-${{ needs.read_versions.outputs.versionMac }}.dmg" "./binaries/Stirling-PDF-mac-x86_64-installer.dmg" + chmod +x ./gradlew + echo "🔧 Building Stirling-PDF JAR..." + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + + # Find the built JAR + STIRLING_JAR=$(ls app/core/build/libs/stirling-pdf-*.jar | head -n 1) + echo "✅ Built JAR: $STIRLING_JAR" + + # Create Tauri directories + mkdir -p ./frontend/src-tauri/libs + mkdir -p ./frontend/src-tauri/runtime + + # Copy JAR to Tauri libs + cp "$STIRLING_JAR" ./frontend/src-tauri/libs/ + echo "✅ JAR copied to Tauri libs" + + # Analyze JAR dependencies for jlink modules + echo "🔍 Analyzing JAR dependencies..." + if command -v jdeps &> /dev/null; then + DETECTED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") + if [ -n "$DETECTED_MODULES" ]; then + echo "📋 jdeps detected modules: $DETECTED_MODULES" + MODULES="$DETECTED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + else + echo "⚠️ jdeps analysis failed, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi else - mv "./build/jpackage/stirling-pdf_${{ needs.read_versions.outputs.version }}-1_amd64.deb" "./binaries/Stirling-PDF-linux-installer.deb" + echo "⚠️ jdeps not available, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" fi - - name: Display structure of downloaded files - run: ls -R ./binaries + # Create custom JRE with jlink + echo "🔧 Creating custom JRE with jlink..." + echo "📋 Using modules: $MODULES" + + # Remove any existing JRE + rm -rf ./frontend/src-tauri/runtime/jre + + # Create the custom JRE + jlink \ + --add-modules "$MODULES" \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output ./frontend/src-tauri/runtime/jre + + if [ ! -d "./frontend/src-tauri/runtime/jre" ]; then + echo "❌ Failed to create JLink runtime" + exit 1 + fi + + # Test the bundled runtime + if [ -f "./frontend/src-tauri/runtime/jre/bin/java" ]; then + RUNTIME_VERSION=$(./frontend/src-tauri/runtime/jre/bin/java --version 2>&1 | head -n 1) + echo "✅ Custom JRE created successfully: $RUNTIME_VERSION" + else + echo "❌ Custom JRE executable not found" + exit 1 + fi + + # Calculate runtime size + RUNTIME_SIZE=$(du -sh ./frontend/src-tauri/runtime/jre | cut -f1) + echo "📊 Custom JRE size: $RUNTIME_SIZE" + env: + DISABLE_ADDITIONAL_FEATURES: true + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm install + + # DigiCert KeyLocker Setup (Cloud HSM) + - name: Setup DigiCert KeyLocker + id: digicert-setup + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }} + uses: digicert/ssm-code-signing@v1.1.0 + env: + SM_API_KEY: ${{ secrets.SM_API_KEY }} + SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} + SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} + SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }} + SM_HOST: ${{ secrets.SM_HOST }} + + - name: Setup DigiCert KeyLocker Certificate + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }} + shell: pwsh + run: | + Write-Host "Setting up DigiCert KeyLocker environment..." + + # Decode client certificate + $certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}") + $certPath = "D:\Certificate_pkcs12.p12" + [IO.File]::WriteAllBytes($certPath, $certBytes) + + # Set environment variables + echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV + echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV + echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV + + # Get PKCS11 config path from DigiCert action + $pkcs11Config = $env:PKCS11_CONFIG + if ($pkcs11Config) { + Write-Host "Found PKCS11_CONFIG: $pkcs11Config" + echo "PKCS11_CONFIG=$pkcs11Config" >> $env:GITHUB_ENV + } else { + Write-Host "PKCS11_CONFIG not set by DigiCert action, using default path" + $defaultPath = "C:\Users\RUNNER~1\AppData\Local\Temp\smtools-windows-x64\pkcs11properties.cfg" + if (Test-Path $defaultPath) { + Write-Host "Found config at default path: $defaultPath" + echo "PKCS11_CONFIG=$defaultPath" >> $env:GITHUB_ENV + } else { + Write-Host "Warning: Could not find PKCS11 config file" + } + } + + # Traditional PFX Certificate Import (fallback if KeyLocker not configured) + - name: Import Windows Code Signing Certificate + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY == '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }} + env: + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + shell: powershell + run: | + if ($env:WINDOWS_CERTIFICATE) { + Write-Host "Importing Windows Code Signing Certificate..." + + # Decode base64 certificate and save to file + $certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE) + $certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx" + [IO.File]::WriteAllBytes($certPath, $certBytes) + + # Import certificate to CurrentUser\My store + $cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force) + + # Extract and set thumbprint as environment variable + $thumbprint = $cert.Thumbprint + Write-Host "Certificate imported with thumbprint: $thumbprint" + echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV + + # Clean up certificate file + Remove-Item $certPath + + Write-Host "Windows certificate import completed." + } else { + Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary" + } + + - name: Import Apple Developer Certificate + if: (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + echo "Importing Apple Developer Certificate..." + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + 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 + security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # Clean up + rm certificate.p12 + + - name: Verify Certificate + if: (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') + run: | + echo "Verifying Apple Developer Certificate..." + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + CERT_INFO=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application") + echo "Certificate Info: $CERT_INFO" + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + echo "Certificate ID: $CERT_ID" + echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV + echo "Certificate imported successfully." + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY }} + VITE_SAAS_SERVER_URL: ${{ secrets.VITE_SAAS_SERVER_URL }} + # Only enable Windows signing in Tauri when on release or V2-master + SIGN: ${{ (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') && (env.SM_API_KEY == '' && env.WINDOWS_CERTIFICATE != '') && '1' || '0' }} + CI: true + with: + projectPath: ./frontend + tauriScript: npx tauri + args: ${{ matrix.args }} + + # Sign with DigiCert KeyLocker (post-build) + - name: Sign Windows binaries with DigiCert KeyLocker + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }} + shell: pwsh + run: | + Write-Host "=== DigiCert KeyLocker Signing ===" + + # Test smctl connectivity first + Write-Host "Testing smctl connection..." + $healthCheck = & smctl healthcheck 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "[SUCCESS] Connected to DigiCert KeyLocker" + } else { + Write-Host "[ERROR] Failed to connect to DigiCert KeyLocker" + Write-Host $healthCheck + exit 1 + } + Write-Host "" + + # Sync certificates to Windows certificate store + Write-Host "Syncing certificates to Windows certificate store..." + $syncOutput = & smctl windows certsync 2>&1 + Write-Host "Cert sync result: $syncOutput" + Write-Host "" + + # Find only the files we need to sign + $filesToSign = @() + + # Main application executable + $mainExe = Get-ChildItem -Path "./frontend/src-tauri/target/x86_64-pc-windows-msvc/release" -Filter "stirling-pdf.exe" -File -ErrorAction SilentlyContinue + if ($mainExe) { $filesToSign += $mainExe } + + # MSI installer + $msiFiles = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*.msi" -Recurse -File + $filesToSign += $msiFiles + + if ($filesToSign.Count -eq 0) { + Write-Host "[ERROR] No files found to sign" + exit 1 + } + + Write-Host "Found $($filesToSign.Count) files to sign:" + foreach ($f in $filesToSign) { Write-Host " - $($f.Name)" } + Write-Host "" + + $signedCount = 0 + foreach ($file in $filesToSign) { + Write-Host "Signing: $($file.Name)" + + # Get PKCS11 config file path + $pkcs11Config = $env:PKCS11_CONFIG + if (-not $pkcs11Config) { + Write-Host "[ERROR] PKCS11_CONFIG environment variable not set" + exit 1 + } + + Write-Host "Using PKCS11 config: $pkcs11Config" + + # Try signing with certificate fingerprint first (if available) + $fingerprint = "${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" + if ($fingerprint -and $fingerprint -ne "") { + Write-Host "Attempting to sign with certificate fingerprint..." + $output = & smctl sign --fingerprint "$fingerprint" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 + $exitCode = $LASTEXITCODE + } else { + Write-Host "No fingerprint provided, using keypair alias..." + $output = & smctl sign --keypair-alias "${{ secrets.SM_KEYPAIR_ALIAS }}" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 + $exitCode = $LASTEXITCODE + } + + Write-Host "Exit code: $exitCode" + Write-Host "Output: $output" + + if ($output -match "FAILED" -or $output -match "error" -or $output -match "Error") { + Write-Host "[ERROR] Signing failed for $($file.Name)" + exit 1 + } + + if ($exitCode -ne 0) { + Write-Host "[ERROR] Failed to sign $($file.Name)" + Write-Host "Full error output:" + Write-Host $output + exit 1 + } + + $signedCount++ + Write-Host "[SUCCESS] Signed: $($file.Name)" + Write-Host "" + } + + Write-Host "=== Summary ===" + Write-Host "[SUCCESS] Signed $signedCount/$($filesToSign.Count) files successfully" + + - name: Rename artifacts + shell: bash + run: | + mkdir -p ./dist + cd ./frontend/src-tauri/target + + # Find and rename artifacts based on platform + if [ "${{ matrix.platform }}" = "windows-latest" ]; then + find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; + find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; + elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then + find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; + find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; + else + find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; + find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; + fi - name: Upload build artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: + name: Stirling-PDF-${{ matrix.name }} + path: ./dist/* retention-days: 1 - if-no-files-found: error - name: ${{ matrix.platform }}binaries - path: | - ./binaries/* - - sign_verify: - needs: [read_versions, build-installers] - strategy: - matrix: - include: - - os: windows-latest - platform: win- - - os: macos-latest - platform: mac- - # - os: ubuntu-latest - # platform: linux- - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 - with: - egress-policy: audit - - - name: Download build artifacts - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 - with: - name: ${{ matrix.platform }}binaries - - - name: Display structure of downloaded files - run: ls -R - - - name: Install Cosign - if: matrix.os == 'windows-latest' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - - - name: Generate key pair - if: matrix.os == 'windows-latest' - run: cosign generate-key-pair - - - name: Sign and generate attestations - if: matrix.os == 'windows-latest' - run: | - cosign sign-blob \ - --key ./cosign.key \ - --yes \ - --output-signature ./Stirling-PDF-win-installer.exe.sig \ - ./Stirling-PDF-win-installer.exe - - cosign attest-blob \ - --predicate - \ - --key ./cosign.key \ - --yes \ - --output-attestation ./Stirling-PDF-win-installer.exe.intoto.jsonl \ - ./Stirling-PDF-win-installer.exe - - cosign verify-blob \ - --key ./cosign.pub \ - --signature ./Stirling-PDF-win-installer.exe.sig \ - ./Stirling-PDF-win-installer.exe - - - name: Display structure of downloaded files - run: ls -R - - - name: Upload signed artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - retention-days: 1 - if-no-files-found: error - name: ${{ matrix.platform }}signed - path: | - ./Stirling-PDF-${{ matrix.platform }}installer.* - ./Stirling-PDF-${{ matrix.platform }}x86_64-installer.* - !cosign.* create-release: - if: github.event_name != 'workflow_dispatch' || github.event.inputs.test_mode != 'true' - needs: [read_versions, sign_verify, sign_verify-portable] + if: (github.event_name == 'workflow_dispatch' && github.event.inputs.test_mode != 'true') || github.event_name == 'release' || github.ref == 'refs/heads/V2-master' + needs: [determine-matrix, build, build-jars] runs-on: ubuntu-latest permissions: contents: write @@ -305,14 +518,37 @@ jobs: with: egress-policy: audit - - name: Download signed artifacts + - name: Download all Tauri artifacts uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + pattern: Stirling-PDF-* + path: ./artifacts/tauri + + - name: Download JAR artifact (no login) + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: jar + path: ./artifacts/jars + + - name: Download JAR artifact (with login) + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: jar-with-login + path: ./artifacts/jars + - name: Display structure of downloaded files - run: ls -R - - name: Upload binaries, attestations and signatures to Release and create GitHub Release + run: ls -R ./artifacts + + - name: Upload binaries to Release uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 with: - tag_name: v${{ needs.read_versions.outputs.version }} + tag_name: v${{ needs.determine-matrix.outputs.version }} generate_release_notes: true files: | - ./*signed/* + ./artifacts/**/*.jar + ./artifacts/**/*.msi + ./artifacts/**/*.dmg + ./artifacts/**/*.deb + ./artifacts/**/*.AppImage + draft: false + prerelease: false diff --git a/.github/workflows/push-docker-v2.yml b/.github/workflows/push-docker-v2.yml new file mode 100644 index 000000000..23d89ef88 --- /dev/null +++ b/.github/workflows/push-docker-v2.yml @@ -0,0 +1,244 @@ +name: Push Docker Image - V2 Branch + +on: + workflow_dispatch: + push: + branches: + - V2-master + +# cancel in-progress jobs if a new job is triggered +# This is useful to avoid running multiple builds for the same branch if a new commit is pushed +# or a pull request is updated. +# It helps to save resources and time by ensuring that only the latest commit is built and tested +# This is particularly useful for long-running jobs that may take a while to complete. +# The `group` is set to a combination of the workflow name, event name, and branch name. +# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of +# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + push: + runs-on: ubuntu-24.04-8core + permissions: + packages: write + id-token: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up JDK 21 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + with: + java-version: "21" + distribution: "temurin" + + - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + with: + gradle-version: 8.14 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + - name: Get version number + id: versionNumber + run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT + + - name: Install cosign + if: github.ref == 'refs/heads/V2-master' + uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + with: + cosign-release: "v2.4.1" + + - name: Login to Docker Hub + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_API }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + + - name: Convert repository owner to lowercase + id: repoowner + run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT + + - name: Generate tags for latest (V2-master branch - production) + id: meta + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-master' + with: + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf + ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }} + type=raw,value=latest + + - name: Generate tags for latest (V2-demo branch - test) + id: meta-test + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-demo' + with: + images: | + ghcr.io/stirling-tools/stirling-pdf-test + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }} + type=raw,value=latest + + - name: Build and push Unified Dockerfile (latest variant) + id: build-push-latest + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: ./docker/Dockerfile.unified + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ github.ref == 'refs/heads/V2-master' && steps.meta.outputs.tags || steps.meta-test.outputs.tags }} + labels: ${{ github.ref == 'refs/heads/V2-master' && steps.meta.outputs.labels || steps.meta-test.outputs.labels }} + build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} + platforms: linux/amd64,linux/arm64/v8 + provenance: true + sbom: true + + - name: Sign regular images + if: github.ref == 'refs/heads/V2-master' + env: + DIGEST: ${{ steps.build-push-latest.outputs.digest }} + TAGS: ${{ steps.meta.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + echo "$TAGS" | tr ',' '\n' | while read -r tag; do + cosign sign --yes \ + --key env://COSIGN_PRIVATE_KEY \ + "${tag}@${DIGEST}" + done + + - name: Generate tags for latest-fat (V2-master branch - production) + id: meta-fat + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-master' + with: + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf + ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat + type=raw,value=latest-fat + + - name: Generate tags for latest-fat (V2-demo branch - test) + id: meta-fat-test + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-demo' + with: + images: | + ghcr.io/stirling-tools/stirling-pdf-test + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat + type=raw,value=latest-fat + + - name: Build and push Unified Dockerfile (fat variant) + id: build-push-fat + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: ./docker/Dockerfile.unified + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ github.ref == 'refs/heads/V2-master' && steps.meta-fat.outputs.tags || steps.meta-fat-test.outputs.tags }} + labels: ${{ github.ref == 'refs/heads/V2-master' && steps.meta-fat.outputs.labels || steps.meta-fat-test.outputs.labels }} + build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} + platforms: linux/amd64,linux/arm64/v8 + provenance: true + sbom: true + + - name: Sign fat images + if: github.ref == 'refs/heads/V2-master' + env: + DIGEST: ${{ steps.build-push-fat.outputs.digest }} + TAGS: ${{ steps.meta-fat.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + echo "$TAGS" | tr ',' '\n' | while read -r tag; do + cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${tag}@${DIGEST}" + done + + - name: Generate tags for ultra-lite (V2-master branch - production) + id: meta-lite + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-master' + with: + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf + ${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite + type=raw,value=latest-ultra-lite + + - name: Generate tags for ultra-lite (V2-demo branch - test) + id: meta-lite-test + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + if: github.ref == 'refs/heads/V2-demo' + with: + images: | + ghcr.io/stirling-tools/stirling-pdf-test + tags: | + type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite + type=raw,value=latest-ultra-lite + + - name: Build and push Unified Dockerfile (ultra-lite variant) + id: build-push-lite + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: ./docker/Dockerfile.unified-lite + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ github.ref == 'refs/heads/V2-master' && steps.meta-lite.outputs.tags || steps.meta-lite-test.outputs.tags }} + labels: ${{ github.ref == 'refs/heads/V2-master' && steps.meta-lite.outputs.labels || steps.meta-lite-test.outputs.labels }} + build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} + platforms: linux/amd64,linux/arm64/v8 + provenance: true + sbom: true + + - name: Sign ultra-lite images + if: github.ref == 'refs/heads/V2-master' + env: + DIGEST: ${{ steps.build-push-lite.outputs.digest }} + TAGS: ${{ steps.meta-lite.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + echo "$TAGS" | tr ',' '\n' | while read -r tag; do + cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${tag}@${DIGEST}" + done diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml index faedf892d..70cfced99 100644 --- a/.github/workflows/releaseArtifacts.yml +++ b/.github/workflows/releaseArtifacts.yml @@ -2,85 +2,445 @@ name: Release Artifacts on: workflow_dispatch: - release: - types: [created] + inputs: + platform: + description: "Platform to build (windows, macos, linux, or all)" + required: true + default: "all" + type: choice + options: + - all + - windows + - macos + - linux + push: + branches: [main, V2, V2-demo] permissions: contents: read jobs: - build: + determine-matrix: runs-on: ubuntu-latest - strategy: - matrix: - disable_security: [true, false] - include: - - disable_security: false - file_suffix: "-with-login" - - disable_security: true - file_suffix: "" outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} version: ${{ steps.versionNumber.outputs.versionNumber }} steps: - - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 - with: - egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Set up JDK 17 - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - with: - java-version: "17" - distribution: "temurin" - - - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - with: - gradle-version: 8.14 - - - name: Generate jar (Disable Security=${{ matrix.disable_security }}) - run: ./gradlew clean createExe - env: - DISABLE_ADDITIONAL_FEATURES: ${{ matrix.disable_security }} - STIRLING_PDF_DESKTOP_UI: false - - name: Get version number id: versionNumber run: | VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}') echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT - - name: Rename binaries + - name: Determine build matrix + id: set-matrix run: | - mv ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe - mv ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + case "${{ github.event.inputs.platform }}" in + "windows") + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "macos") + echo 'matrix={"include":[{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "linux") + echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + *) + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + esac + else + # For push events, build all platforms + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + fi - - name: Debug build artifacts + build: + needs: determine-matrix + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.determine-matrix.outputs.matrix) }} + runs-on: ${{ matrix.platform }} + env: + SM_API_KEY: ${{ secrets.SM_API_KEY }} + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' run: | - echo "Current Directory: $(pwd)" - ls -R ./build/libs - ls -R ./build/launch4j + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + java-version: "21" + distribution: "temurin" + + - name: Build Java backend with JLink + working-directory: ./ + shell: bash + run: | + chmod +x ./gradlew + echo "🔧 Building Stirling-PDF JAR..." + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + + # Find the built JAR + STIRLING_JAR=$(ls app/core/build/libs/stirling-pdf-*.jar | head -n 1) + echo "✅ Built JAR: $STIRLING_JAR" + + # Create Tauri directories + mkdir -p ./frontend/src-tauri/libs + mkdir -p ./frontend/src-tauri/runtime + + # Copy JAR to Tauri libs + cp "$STIRLING_JAR" ./frontend/src-tauri/libs/ + echo "✅ JAR copied to Tauri libs" + + # Analyze JAR dependencies for jlink modules + echo "🔍 Analyzing JAR dependencies..." + if command -v jdeps &> /dev/null; then + DETECTED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") + if [ -n "$DETECTED_MODULES" ]; then + echo "📋 jdeps detected modules: $DETECTED_MODULES" + MODULES="$DETECTED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + else + echo "⚠️ jdeps analysis failed, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi + else + echo "⚠️ jdeps not available, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi + + # Create custom JRE with jlink + echo "🔧 Creating custom JRE with jlink..." + echo "📋 Using modules: $MODULES" + + # Remove any existing JRE + rm -rf ./frontend/src-tauri/runtime/jre + + # Create the custom JRE + jlink \ + --add-modules "$MODULES" \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output ./frontend/src-tauri/runtime/jre + + if [ ! -d "./frontend/src-tauri/runtime/jre" ]; then + echo "❌ Failed to create JLink runtime" + exit 1 + fi + + # Test the bundled runtime + if [ -f "./frontend/src-tauri/runtime/jre/bin/java" ]; then + RUNTIME_VERSION=$(./frontend/src-tauri/runtime/jre/bin/java --version 2>&1 | head -n 1) + echo "✅ Custom JRE created successfully: $RUNTIME_VERSION" + else + echo "❌ Custom JRE executable not found" + exit 1 + fi + + # Calculate runtime size + RUNTIME_SIZE=$(du -sh ./frontend/src-tauri/runtime/jre | cut -f1) + echo "📊 Custom JRE size: $RUNTIME_SIZE" + env: + DISABLE_ADDITIONAL_FEATURES: true + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm install + + # DigiCert KeyLocker Setup (Cloud HSM) + - name: Setup DigiCert KeyLocker + id: digicert-setup + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} + uses: digicert/ssm-code-signing@v1.1.0 + env: + SM_API_KEY: ${{ secrets.SM_API_KEY }} + SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} + SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} + SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }} + SM_HOST: ${{ secrets.SM_HOST }} + + - name: Setup DigiCert KeyLocker Certificate + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} + shell: pwsh + run: | + Write-Host "Setting up DigiCert KeyLocker environment..." + + # Decode client certificate + $certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}") + $certPath = "D:\Certificate_pkcs12.p12" + [IO.File]::WriteAllBytes($certPath, $certBytes) + + # Set environment variables + echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV + echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV + echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV + echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV + + # Get PKCS11 config path from DigiCert action + $pkcs11Config = $env:PKCS11_CONFIG + if ($pkcs11Config) { + Write-Host "Found PKCS11_CONFIG: $pkcs11Config" + echo "PKCS11_CONFIG=$pkcs11Config" >> $env:GITHUB_ENV + } else { + Write-Host "PKCS11_CONFIG not set by DigiCert action, using default path" + $defaultPath = "C:\Users\RUNNER~1\AppData\Local\Temp\smtools-windows-x64\pkcs11properties.cfg" + if (Test-Path $defaultPath) { + Write-Host "Found config at default path: $defaultPath" + echo "PKCS11_CONFIG=$defaultPath" >> $env:GITHUB_ENV + } else { + Write-Host "Warning: Could not find PKCS11 config file" + } + } + + # Traditional PFX Certificate Import (fallback if KeyLocker not configured) + - name: Import Windows Code Signing Certificate + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY == '' && github.ref == 'refs/heads/main' }} + env: + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + shell: powershell + run: | + if ($env:WINDOWS_CERTIFICATE) { + Write-Host "Importing Windows Code Signing Certificate..." + + # Decode base64 certificate and save to file + $certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE) + $certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx" + [IO.File]::WriteAllBytes($certPath, $certBytes) + + # Import certificate to CurrentUser\My store + $cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force) + + # Extract and set thumbprint as environment variable + $thumbprint = $cert.Thumbprint + Write-Host "Certificate imported with thumbprint: $thumbprint" + echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV + + # Clean up certificate file + Remove-Item $certPath + + Write-Host "Windows certificate import completed." + } else { + Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary" + } + + - name: Import Apple Developer Certificate + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + echo "Importing Apple Developer Certificate..." + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + 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 + security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # Clean up + rm certificate.p12 + + - name: Verify Certificate + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + run: | + echo "Verifying Apple Developer Certificate..." + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + CERT_INFO=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application") + echo "Certificate Info: $CERT_INFO" + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + echo "Certificate ID: $CERT_ID" + echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV + echo "Certificate imported successfully." + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY }} + VITE_SAAS_SERVER_URL: ${{ secrets.VITE_SAAS_SERVER_URL }} + # Only enable Windows signing in Tauri when on main + SIGN: ${{ github.ref == 'refs/heads/main' && (env.SM_API_KEY == '' && env.WINDOWS_CERTIFICATE != '') && '1' || '0' }} + CI: true + with: + projectPath: ./frontend + tauriScript: npx tauri + args: ${{ matrix.args }} + + # Sign with DigiCert KeyLocker (post-build) + - name: Sign Windows binaries with DigiCert KeyLocker + if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} + shell: pwsh + run: | + Write-Host "=== DigiCert KeyLocker Signing ===" + + # Test smctl connectivity first + Write-Host "Testing smctl connection..." + $healthCheck = & smctl healthcheck 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "[SUCCESS] Connected to DigiCert KeyLocker" + } else { + Write-Host "[ERROR] Failed to connect to DigiCert KeyLocker" + Write-Host $healthCheck + exit 1 + } + Write-Host "" + + # Sync certificates to Windows certificate store + Write-Host "Syncing certificates to Windows certificate store..." + $syncOutput = & smctl windows certsync 2>&1 + Write-Host "Cert sync result: $syncOutput" + Write-Host "" + + # Find only the files we need to sign (not build scripts) + $filesToSign = @() + + # Main application executable + $mainExe = Get-ChildItem -Path "./frontend/src-tauri/target/x86_64-pc-windows-msvc/release" -Filter "stirling-pdf.exe" -File -ErrorAction SilentlyContinue + if ($mainExe) { $filesToSign += $mainExe } + + # MSI installer + $msiFiles = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*.msi" -Recurse -File + $filesToSign += $msiFiles + + if ($filesToSign.Count -eq 0) { + Write-Host "[ERROR] No files found to sign" + exit 1 + } + + Write-Host "Found $($filesToSign.Count) files to sign:" + foreach ($f in $filesToSign) { Write-Host " - $($f.Name)" } + Write-Host "" + + $signedCount = 0 + foreach ($file in $filesToSign) { + Write-Host "Signing: $($file.Name)" + + # Get PKCS11 config file path (set by DigiCert action) + $pkcs11Config = $env:PKCS11_CONFIG + if (-not $pkcs11Config) { + Write-Host "[ERROR] PKCS11_CONFIG environment variable not set" + Write-Host "DigiCert KeyLocker action may not have run correctly" + exit 1 + } + + Write-Host "Using PKCS11 config: $pkcs11Config" + + # Try signing with certificate fingerprint first (if available) + $fingerprint = "${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" + if ($fingerprint -and $fingerprint -ne "") { + Write-Host "Attempting to sign with certificate fingerprint..." + $output = & smctl sign --fingerprint "$fingerprint" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 + $exitCode = $LASTEXITCODE + } else { + Write-Host "No fingerprint provided, using keypair alias..." + # Use smctl to sign with keypair alias + $output = & smctl sign --keypair-alias "${{ secrets.SM_KEYPAIR_ALIAS }}" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 + $exitCode = $LASTEXITCODE + } + + Write-Host "Exit code: $exitCode" + Write-Host "Output: $output" + + # Check if output contains "FAILED" even with exit code 0 + if ($output -match "FAILED" -or $output -match "error" -or $output -match "Error") { + Write-Host "" + Write-Host "[ERROR] Signing failed for $($file.Name)" + Write-Host "[ERROR] smctl returned success but output indicates failure" + exit 1 + } + + if ($exitCode -ne 0) { + Write-Host "[ERROR] Failed to sign $($file.Name)" + Write-Host "Full error output:" + Write-Host $output + exit 1 + } + + $signedCount++ + Write-Host "[SUCCESS] Signed: $($file.Name)" + Write-Host "" + } + + Write-Host "=== Summary ===" + Write-Host "[SUCCESS] Signed $signedCount/$($filesToSign.Count) files successfully" + + - name: Rename artifacts + shell: bash + run: | + mkdir -p ./dist + cd ./frontend/src-tauri/target + + # Find and rename artifacts based on platform + if [ "${{ matrix.platform }}" = "windows-latest" ]; then + find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; + find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; + elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then + find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; + find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; + else + find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; + find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; + fi - name: Upload build artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: binaries${{ matrix.file_suffix }} - path: | - ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.* - ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.* + name: Stirling-PDF-${{ matrix.name }} + path: ./dist/* + retention-days: 30 sign_verify: needs: build runs-on: ubuntu-latest + if: success() strategy: + fail-fast: false matrix: - disable_security: [true, false] - include: - - disable_security: false - file_suffix: "-with-login" - - disable_security: true - file_suffix: "" + name: [windows-x86_64, macos-aarch64, macos-x86_64, linux-x86_64] steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 @@ -90,7 +450,8 @@ jobs: - name: Download build artifacts uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: - name: binaries${{ matrix.file_suffix }} + name: Stirling-PDF-${{ matrix.name }} + - name: Display structure of downloaded files run: ls -R @@ -101,80 +462,74 @@ jobs: run: cosign generate-key-pair - name: Sign and generate attestations + shell: bash run: | - cosign sign-blob \ - --key ./cosign.key \ - --yes \ - --output-signature ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.sig \ - ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar + # Sign all artifacts for this platform + for file in *; do + if [ -f "$file" ] && [[ ! "$file" =~ \.(sig|intoto\.jsonl)$ ]]; then + echo "Signing: $file" - cosign attest-blob \ - --predicate - \ - --key ./cosign.key \ - --yes \ - --output-attestation ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.intoto.jsonl \ - ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar + # Sign the artifact + cosign sign-blob \ + --key ./cosign.key \ + --yes \ + --output-signature "${file}.sig" \ + "$file" - cosign verify-blob \ - --key ./cosign.pub \ - --signature ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar.sig \ - ./libs/Stirling-PDF${{ matrix.file_suffix }}.jar + # Generate attestation + cosign attest-blob \ + --predicate - \ + --key ./cosign.key \ + --yes \ + --output-attestation "${file}.intoto.jsonl" \ + "$file" - cosign sign-blob \ - --key ./cosign.key \ - --yes \ - --output-signature ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.sig \ - ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe + # Verify the signature + cosign verify-blob \ + --key ./cosign.pub \ + --signature "${file}.sig" \ + "$file" - cosign attest-blob \ - --predicate - \ - --key ./cosign.key \ - --yes \ - --output-attestation ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.intoto.jsonl \ - ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe - - cosign verify-blob \ - --key ./cosign.pub \ - --signature ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe.sig \ - ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe + echo "✅ Signed and verified: $file" + fi + done - name: Upload signed artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: signed${{ matrix.file_suffix }} + name: Stirling-PDF-${{ matrix.name }}-signed path: | - ./libs/Stirling-PDF${{ matrix.file_suffix }}.* - ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.* + * + !cosign.key + !cosign.pub + retention-days: 30 release: - needs: [build, sign_verify] + needs: [determine-matrix, build, sign_verify] runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' permissions: contents: write - strategy: - matrix: - disable_security: [true, false] - include: - - disable_security: false - file_suffix: "-with-login" - - disable_security: true - file_suffix: "" steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - - name: Download signed artifacts + - name: Download all signed artifacts uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: - name: signed${{ matrix.file_suffix }} + pattern: Stirling-PDF-*-signed + path: ./artifacts - - name: Upload binaries, attestations and signatures to Release and create GitHub Release + - name: Display structure of downloaded files + run: ls -R ./artifacts + + - name: Create GitHub Release uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 with: - tag_name: v${{ needs.build.outputs.version }} + tag_name: v${{ needs.determine-matrix.outputs.version }} generate_release_notes: true - files: | - ./libs/Stirling-PDF* - ./launch4j/Stirling-PDF-Server* + files: ./artifacts/**/* + draft: false + prerelease: false diff --git a/docker/Dockerfile.unified-lite b/docker/Dockerfile.unified-lite new file mode 100644 index 000000000..a83b2544b --- /dev/null +++ b/docker/Dockerfile.unified-lite @@ -0,0 +1,108 @@ +# Unified Ultra-Lite Dockerfile - Frontend + Backend in single container with minimal dependencies +# Supports MODE parameter: BOTH (default), FRONTEND, BACKEND + +# Stage 1: Build Frontend +FROM node:20-alpine AS frontend-build + +WORKDIR /app + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend . +RUN DISABLE_ADDITIONAL_FEATURES=true npm run build + +# Stage 2: Build Backend +FROM gradle:8.14-jdk21 AS backend-build + +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +WORKDIR /app +COPY . . + +RUN DISABLE_ADDITIONAL_FEATURES=true \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Stage 3: Final unified ultra-lite image +FROM alpine:3.22.1 + +ARG VERSION_TAG + +# Labels +LABEL org.opencontainers.image.title="Stirling-PDF Unified Ultra-Lite" +LABEL org.opencontainers.image.description="Unified ultra-lite container for Stirling-PDF - Frontend + Backend with minimal dependencies" +LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.vendor="Stirling-Tools" +LABEL org.opencontainers.image.url="https://www.stirlingpdf.com" +LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com" +LABEL maintainer="Stirling-Tools" +LABEL org.opencontainers.image.authors="Stirling-Tools" +LABEL org.opencontainers.image.version="${VERSION_TAG}" +LABEL org.opencontainers.image.keywords="PDF, manipulation, unified, ultra-lite, API, Spring Boot, React" + +# Copy backend files +COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh +COPY scripts/installFonts.sh /scripts/installFonts.sh +COPY --from=backend-build /app/app/core/build/libs/*.jar app.jar + +# Copy frontend files +COPY --from=frontend-build /app/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY docker/unified/nginx.conf /etc/nginx/nginx.conf +COPY docker/unified/entrypoint.sh /entrypoint.sh + +# Environment Variables +ENV DISABLE_ADDITIONAL_FEATURES=false \ + VERSION_TAG=$VERSION_TAG \ + JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ + JAVA_CUSTOM_OPTS="" \ + HOME=/home/stirlingpdfuser \ + PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf \ + MODE=BOTH \ + BACKEND_INTERNAL_PORT=8081 \ + VITE_API_BASE_URL=http://localhost:8080 \ + ENDPOINTS_GROUPS_TO_REMOVE=CLI + +# Install minimal dependencies +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ + echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ + apk upgrade --no-cache -a && \ + apk add --no-cache \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + shadow \ + su-exec \ + openjdk21-jre \ + nginx && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf /pipeline/watchedFolders /pipeline/finishedFolders && \ + mkdir -p /usr/share/fonts/opentype/noto /var/lib/nginx/tmp /var/log/nginx && \ + chmod +x /scripts/*.sh && \ + chmod +x /entrypoint.sh && \ + # User permissions + addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /pipeline /configs /customFiles /pipeline /tmp/stirling-pdf /var/lib/nginx /var/log/nginx /usr/share/nginx && \ + chown stirlingpdfuser:stirlingpdfgroup /app.jar + +EXPOSE 8080/tcp + +ENTRYPOINT ["tini", "--", "/entrypoint.sh"]