Build fio Static Binaries #1235
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: Build fio Static Binaries | |
| on: | |
| workflow_dispatch: | |
| jobs: | |
| check-release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| latest-version: ${{ steps.get-version.outputs.version }} | |
| should-build: ${{ steps.check-version.outputs.should-build }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Get latest fio release | |
| id: get-version | |
| run: | | |
| # Get the latest release tag from GitHub API | |
| LATEST_VERSION=$(curl -s https://api.github.com/repos/axboe/fio/releases/latest | jq -r '.tag_name') | |
| echo "Latest fio version: $LATEST_VERSION" | |
| echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT | |
| - name: Check if version exists in releases | |
| id: check-version | |
| run: | | |
| VERSION="${{ steps.get-version.outputs.version }}" | |
| # Check if this version already exists in our releases | |
| if gh release view "fio-$VERSION" --repo ${{ github.repository }} >/dev/null 2>&1; then | |
| echo "Version $VERSION already exists in releases" | |
| echo "should-build=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Version $VERSION does not exist, should build" | |
| echo "should-build=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| build: | |
| needs: check-release | |
| if: needs.check-release.outputs.should-build == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| include: | |
| - arch: x64 | |
| cross: x86_64-linux-musl | |
| host: x86_64-linux-musl | |
| - arch: x86 | |
| cross: i686-linux-musl | |
| host: i686-linux-musl | |
| - arch: aarch64 | |
| cross: aarch64-linux-musl | |
| host: aarch64-linux-gnu | |
| - arch: arm | |
| cross: arm-linux-musleabihf | |
| host: arm-linux-gnueabihf | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Create compilation script | |
| run: | | |
| VERSION="${{ needs.check-release.outputs.latest-version }}" | |
| ARCH="${{ matrix.arch }}" | |
| CROSS="${{ matrix.cross }}" | |
| HOST="${{ matrix.host }}" | |
| # Single script for all architectures using musl cross-compilation | |
| cat > compile-fio.sh << EOF | |
| #!/bin/bash | |
| set -e | |
| # Activate Holy Build Box lib compilation environment | |
| source /hbb/activate | |
| set -x | |
| # remove obsolete CentOS repos | |
| cd /etc/yum.repos.d/ | |
| rm -f CentOS-Base.repo CentOS-SCLo-scl-rh.repo CentOS-SCLo-scl.repo CentOS-fasttrack.repo CentOS-x86_64-kernel.repo | |
| yum install -y yum-plugin-ovl # fix for docker overlay fs | |
| yum install -y xz | |
| # download musl cross compilation toolchain | |
| cd ~ | |
| curl -L "https://musl.cc/\$CROSS-cross.tgz" -o "\$CROSS-cross.tgz" | |
| tar xf "\$CROSS-cross.tgz" | |
| # download, compile, and install libaio as static library | |
| cd ~ | |
| curl -L http://ftp.de.debian.org/debian/pool/main/liba/libaio/libaio_0.3.113.orig.tar.gz -o "libaio.tar.gz" | |
| tar xf libaio.tar.gz | |
| cd libaio-*/src | |
| CC=/root/\$CROSS-cross/bin/\$CROSS-gcc ENABLE_SHARED=0 make prefix=/hbb_exe install | |
| # Activate Holy Build Box exe compilation environment | |
| source /hbb_exe/activate | |
| # download and compile fio | |
| cd ~ | |
| curl -L "https://github.com/axboe/fio/archive/\$VERSION.tar.gz" -o "fio.tar.gz" | |
| tar xf fio.tar.gz | |
| cd fio-\${VERSION#fio-}* | |
| CC=/root/\$CROSS-cross/bin/\$CROSS-gcc ./configure --disable-native --build-static | |
| make | |
| # verify no external shared library links | |
| libcheck fio | |
| # copy fio binary to mounted dir | |
| cp fio "/io/fio_\$ARCH" | |
| EOF | |
| chmod +x compile-fio.sh | |
| - name: Compile fio binary | |
| run: | | |
| ARCH="${{ matrix.arch }}" | |
| CROSS="${{ matrix.cross }}" | |
| HOST="${{ matrix.host }}" | |
| # Use musl cross-compilation for all architectures | |
| docker run --rm -v $(pwd):/io --env ARCH=$ARCH --env CROSS=$CROSS --env HOST=$HOST --env VERSION="${{ needs.check-release.outputs.latest-version }}" phusion/holy-build-box-64:latest bash /io/compile-fio.sh | |
| - name: Verify binary | |
| run: | | |
| ARCH="${{ matrix.arch }}" | |
| ls -la fio_$ARCH | |
| file fio_$ARCH | |
| - name: Upload binary as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: fio_${{ matrix.arch }} | |
| path: fio_${{ matrix.arch }} | |
| retention-days: 1 | |
| virustotal-scan: | |
| needs: [check-release, build] | |
| if: needs.check-release.outputs.should-build == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| scan-results: ${{ steps.scan-summary.outputs.results }} | |
| all-clean: ${{ steps.scan-summary.outputs.all-clean }} | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Prepare binaries for scanning | |
| run: | | |
| mkdir -p scan-binaries | |
| find artifacts -name "fio_*" -type f -exec cp {} scan-binaries/ \; | |
| ls -la scan-binaries/ | |
| - name: Upload binaries to VirusTotal and scan | |
| id: virustotal-upload | |
| run: | | |
| echo "## VirusTotal Scan Results" > scan_results.md | |
| echo "| Binary | Status | Malicious | Suspicious | Undetected | VirusTotal URL |" >> scan_results.md | |
| echo "|--------|--------|-----------|------------|------------|----------------|" >> scan_results.md | |
| ALL_CLEAN=true | |
| SCAN_DATA="" | |
| for binary in scan-binaries/fio_*; do | |
| filename=$(basename "$binary") | |
| echo "Uploading $filename to VirusTotal..." | |
| # Upload to VirusTotal | |
| upload_response=$(curl -s --request POST \ | |
| --url https://www.virustotal.com/api/v3/files \ | |
| --header 'accept: application/json' \ | |
| --header 'content-type: multipart/form-data' \ | |
| --header "x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}" \ | |
| --form "file=@$binary") | |
| analysis_id=$(echo "$upload_response" | jq -r '.data.id') | |
| echo "Analysis ID for $filename: $analysis_id" | |
| if [ "$analysis_id" = "null" ] || [ -z "$analysis_id" ]; then | |
| echo "Failed to upload $filename to VirusTotal" | |
| echo "| $filename | Upload Failed | N/A | N/A | N/A | N/A |" >> scan_results.md | |
| ALL_CLEAN=false | |
| continue | |
| fi | |
| # Store analysis ID for later retrieval | |
| echo "${filename}:${analysis_id}" >> analysis_ids.txt | |
| done | |
| echo "all_clean_upload=$ALL_CLEAN" >> $GITHUB_OUTPUT | |
| # Wait 2 minutes for scans to complete | |
| echo "Waiting 2 minutes for VirusTotal scans to complete..." | |
| sleep 120 | |
| - name: Retrieve VirusTotal scan results | |
| id: get-results | |
| run: | | |
| ALL_CLEAN=true | |
| SCAN_SUMMARY="" | |
| while IFS=':' read -r filename analysis_id; do | |
| echo "Retrieving results for $filename (ID: $analysis_id)..." | |
| # Get scan results | |
| result_response=$(curl -s --request GET \ | |
| --url "https://www.virustotal.com/api/v3/analyses/$analysis_id" \ | |
| --header 'accept: application/json' \ | |
| --header "x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}") | |
| status=$(echo "$result_response" | jq -r '.data.attributes.status // "unknown"') | |
| if [ "$status" = "completed" ]; then | |
| malicious=$(echo "$result_response" | jq -r '.data.attributes.stats.malicious // 0') | |
| suspicious=$(echo "$result_response" | jq -r '.data.attributes.stats.suspicious // 0') | |
| undetected=$(echo "$result_response" | jq -r '.data.attributes.stats.undetected // 0') | |
| # Get file hash from the item link | |
| item_link=$(echo "$result_response" | jq -r '.data.links.item // ""') | |
| file_hash=$(echo "$item_link" | sed 's/.*files\///') | |
| vt_url="https://www.virustotal.com/gui/file/$file_hash" | |
| if [ "$malicious" -gt 0 ] || [ "$suspicious" -gt 0 ]; then | |
| status_text="⚠️ FLAGGED" | |
| ALL_CLEAN=false | |
| else | |
| status_text="✅ Clean" | |
| fi | |
| echo "| $filename | $status_text | $malicious | $suspicious | $undetected | [$file_hash]($vt_url) |" >> scan_results.md | |
| else | |
| echo "| $filename | ⏳ Pending | N/A | N/A | N/A | Scan not completed |" >> scan_results.md | |
| ALL_CLEAN=false | |
| fi | |
| done < analysis_ids.txt | |
| echo "all_clean=$ALL_CLEAN" >> $GITHUB_OUTPUT | |
| # Add summary to GitHub step summary | |
| cat scan_results.md >> $GITHUB_STEP_SUMMARY | |
| - name: Create scan summary | |
| id: scan-summary | |
| run: | | |
| RESULTS=$(cat scan_results.md) | |
| ALL_CLEAN="${{ steps.get-results.outputs.all_clean }}" | |
| # Escape newlines for GitHub output | |
| RESULTS_ESCAPED=$(echo "$RESULTS" | sed ':a;N;$!ba;s/\n/\\n/g') | |
| echo "results=$RESULTS_ESCAPED" >> $GITHUB_OUTPUT | |
| echo "all-clean=$ALL_CLEAN" >> $GITHUB_OUTPUT | |
| - name: Upload scan results as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: virustotal-scan-results | |
| path: | | |
| scan_results.md | |
| analysis_ids.txt | |
| retention-days: 30 | |
| create-release: | |
| needs: [check-release, build, virustotal-scan] | |
| if: needs.check-release.outputs.should-build == 'true' && needs.virustotal-scan.outputs.all-clean == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Prepare release assets | |
| run: | | |
| mkdir -p release-assets | |
| # Move all binaries to release-assets directory | |
| find artifacts -name "fio_*" -type f -exec cp {} release-assets/ \; | |
| ls -la release-assets/ | |
| - name: Generate SHA256 checksums | |
| run: | | |
| cd release-assets | |
| sha256sum fio_* > fio-checksums.sha256 | |
| cat fio-checksums.sha256 | |
| - name: Download VirusTotal scan results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: virustotal-scan-results | |
| path: vt-results | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: fio-${{ needs.check-release.outputs.latest-version }} | |
| name: fio ${{ needs.check-release.outputs.latest-version }} Static Binaries | |
| body: | | |
| Static binaries for fio ${{ needs.check-release.outputs.latest-version }} | |
| Built using musl toolchains for maximum compatibility. | |
| **Architectures:** | |
| - `fio_x64` - x86_64 (64-bit) | |
| - `fio_x86` - i686 (32-bit) | |
| - `fio_aarch64` - ARM 64-bit | |
| - `fio_arm` - ARM 32-bit | |
| **Security Verification:** | |
| All binaries have been scanned by VirusTotal and verified clean. | |
| ${{ needs.virustotal-scan.outputs.scan-results }} | |
| **Checksum Verification:** | |
| ```bash | |
| # Verify checksums | |
| sha256sum -c fio-checksums.sha256 | |
| ``` | |
| For usage in YABS script, place these binaries in the `bin/fio/` directory. | |
| files: | | |
| release-assets/fio_* | |
| release-assets/fio-checksums.sha256 | |
| draft: false | |
| prerelease: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| notify-failure: | |
| needs: [check-release, build, virustotal-scan] | |
| if: always() && needs.check-release.outputs.should-build == 'true' && (failure() || needs.virustotal-scan.outputs.all-clean == 'false') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Send failure notification | |
| run: | | |
| echo "## ⚠️ fio Build Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "Version: ${{ needs.check-release.outputs.latest-version }}" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.virustotal-scan.outputs.all-clean }}" == "false" ]; then | |
| echo "**Reason:** VirusTotal scan detected issues with one or more binaries" >> $GITHUB_STEP_SUMMARY | |
| echo "**Scan Results:**" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ needs.virustotal-scan.outputs.scan-results }}" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "**Reason:** Build compilation failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "Check the workflow logs for details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY |