name: Release
on:
push:
tags:
- 'v*'
env:
CARGO_TERM_COLOR: always
permissions:
contents: write
packages: write
jobs:
create-release:
name: Create Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from tag
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Generate changelog
id: changelog
run: |
# Extract changelog for this version
if [ -f CHANGELOG.md ]; then
# Get content between version headers
awk '/^## \['${{ steps.get_version.outputs.version }}'\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md > release_notes.md
echo "has_changelog=true" >> $GITHUB_OUTPUT
else
echo "Release ${{ steps.get_version.outputs.version }}" > release_notes.md
echo "has_changelog=false" >> $GITHUB_OUTPUT
fi
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: qbak v${{ steps.get_version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ contains(steps.get_version.outputs.version, '-') }}
build-release:
name: Build Release
needs: create-release
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
name: qbak-linux-x86_64
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
name: qbak-linux-x86_64-musl
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
name: qbak-linux-arm64
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
name: qbak-linux-arm64-musl
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-latest
name: qbak-linux-armv7l
- target: x86_64-apple-darwin
os: macos-latest
name: qbak-macos-x86_64
- target: aarch64-apple-darwin
os: macos-latest
name: qbak-macos-arm64
- target: x86_64-pc-windows-msvc
os: windows-latest
name: qbak-windows-x86_64
ext: .exe
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools (Linux musl only)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get install -y musl-tools
- name: Install ARM64 musl tools (ARM64 musl only)
if: matrix.target == 'aarch64-unknown-linux-musl'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools gcc-aarch64-linux-gnu
- name: Install ARM64 cross-compilation tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install ARMv7 cross-compilation tools
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt-get update
sudo apt-get install -y gcc-arm-linux-gnueabihf
- name: Configure cross-compilation (ARM64 Linux)
if: contains(matrix.target, 'aarch64-unknown-linux')
run: |
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Configure cross-compilation (ARMv7 Linux)
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
echo "CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc" >> $GITHUB_ENV
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
key: release-${{ matrix.target }}
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
- name: Upload to VirusTotal (Windows only)
if: matrix.os == 'windows-latest'
continue-on-error: true
run: |
if [ -n "${{ secrets.VIRUSTOTAL_API_KEY }}" ]; then
echo "Uploading ${{ matrix.name }}.exe to VirusTotal..."
response=$(curl --silent --request POST \
--url https://www.virustotal.com/api/v3/files \
--header "x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}" \
--form "file=@target/${{ matrix.target }}/release/qbak.exe")
echo "VirusTotal Response:"
echo "$response" | jq .
# Extract analysis ID and provide link
if echo "$response" | jq -e '.data.id' >/dev/null 2>&1; then
analysis_id=$(echo "$response" | jq -r '.data.id')
echo "✅ VirusTotal scan initiated successfully"
echo "📊 Analysis ID: $analysis_id"
echo "🔗 Check results at: https://www.virustotal.com/gui/file-analysis/$analysis_id"
elif echo "$response" | jq -e '.error' >/dev/null 2>&1; then
error_code=$(echo "$response" | jq -r '.error.code // "unknown"')
error_message=$(echo "$response" | jq -r '.error.message // "unknown"')
echo "❌ VirusTotal API Error: $error_code - $error_message"
else
echo "❓ Unexpected response format from VirusTotal"
fi
else
echo "VirusTotal API key not configured, skipping scan"
fi
shell: bash
- name: Strip binary (Unix)
if: matrix.os != 'windows-latest'
run: |
if [[ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]] || [[ "${{ matrix.target }}" == "aarch64-unknown-linux-musl" ]]; then
aarch64-linux-gnu-strip target/${{ matrix.target }}/release/qbak
elif [[ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]]; then
arm-linux-gnueabihf-strip target/${{ matrix.target }}/release/qbak
else
strip target/${{ matrix.target }}/release/qbak
fi
- name: Create archive (Unix)
if: matrix.os != 'windows-latest'
run: |
cd target/${{ matrix.target }}/release
tar czf ../../../${{ matrix.name }}.tar.gz qbak
cd ../../..
- name: Create archive (Windows)
if: matrix.os == 'windows-latest'
run: |
cd target/${{ matrix.target }}/release
7z a ../../../${{ matrix.name }}.zip qbak.exe
cd ../../..
- name: Upload release asset (Unix)
if: matrix.os != 'windows-latest'
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.name }}.tar.gz
- name: Upload release asset (Windows)
if: matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.name }}.zip
publish-crate:
name: Publish to crates.io
needs: [create-release, build-release]
runs-on: ubuntu-latest
if: ${{ !contains(needs.create-release.outputs.version, '-') }} steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
- name: Verify version matches tag
run: |
CARGO_VERSION=$(grep '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
TAG_VERSION="${{ needs.create-release.outputs.version }}"
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
echo "❌ Version mismatch: Cargo.toml has $CARGO_VERSION, tag is $TAG_VERSION"
exit 1
fi
echo "✅ Version check passed: $CARGO_VERSION"
- name: Run tests before publish
run: cargo test --all-features -- --test-threads=1
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
create-checksums:
name: Create Checksums
needs: [create-release, build-release]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Create checksums
run: |
echo "# Checksums for qbak v${{ needs.create-release.outputs.version }}" > checksums.txt
echo "" >> checksums.txt
for file in qbak-*.tar.gz qbak-*.zip; do
if [ -f "$file" ]; then
echo "## $file" >> checksums.txt
sha256sum "$file" >> checksums.txt
echo "" >> checksums.txt
fi
done
- name: Upload checksums
uses: softprops/action-gh-release@v2
with:
files: checksums.txt