# Copyright 2024 RustFS Team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Build and Release on: push: tags: ["*"] branches: [main] paths-ignore: - "**.md" - "**.txt" - ".github/**" - "docs/**" - "deploy/**" - "scripts/dev_*.sh" - "LICENSE*" - "README*" - "**/*.png" - "**/*.jpg" - "**/*.svg" - ".gitignore" - ".dockerignore" pull_request: branches: [main] paths-ignore: - "**.md" - "**.txt" - ".github/**" - "docs/**" - "deploy/**" - "scripts/dev_*.sh" - "LICENSE*" - "README*" - "**/*.png" - "**/*.jpg" - "**/*.svg" - ".gitignore" - ".dockerignore" schedule: - cron: "0 0 * * 0" # Weekly on Sunday at midnight UTC workflow_dispatch: inputs: force_build: description: "Force build even without changes" required: false default: false type: boolean env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 # Optimize build performance CARGO_INCREMENTAL: 0 jobs: # Second layer: Business logic level checks (handling build strategy) build-check: name: Build Strategy Check runs-on: ubuntu-latest outputs: should_build: ${{ steps.check.outputs.should_build }} build_type: ${{ steps.check.outputs.build_type }} steps: - name: Determine build strategy id: check run: | should_build=false build_type="none" # Business logic: when we need to build if [[ "${{ github.event_name }}" == "schedule" ]] || \ [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ [[ "${{ github.event.inputs.force_build }}" == "true" ]] || \ [[ "${{ contains(github.event.head_commit.message, '--build') }}" == "true" ]]; then should_build=true build_type="development" fi # Always build for tag pushes (version releases) if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then should_build=true build_type="release" echo "🏷️ Tag detected: forcing release build" fi echo "should_build=$should_build" >> $GITHUB_OUTPUT echo "build_type=$build_type" >> $GITHUB_OUTPUT echo "Build needed: $should_build (type: $build_type)" # Build RustFS binaries build-rustfs: name: Build RustFS needs: [build-check] if: needs.build-check.outputs.should_build == 'true' runs-on: ${{ matrix.os }} timeout-minutes: 60 env: RUSTFLAGS: ${{ matrix.cross == 'false' && '-C target-cpu=native' || '' }} strategy: fail-fast: false matrix: include: # Linux builds - os: ubuntu-latest target: x86_64-unknown-linux-musl cross: false platform: linux - os: ubuntu-latest target: aarch64-unknown-linux-musl cross: true platform: linux # macOS builds - os: macos-latest target: aarch64-apple-darwin cross: false platform: macos - os: macos-latest target: x86_64-apple-darwin cross: false platform: macos # Windows builds - os: windows-latest target: x86_64-pc-windows-msvc cross: false platform: windows - os: windows-latest target: aarch64-pc-windows-msvc cross: true platform: windows steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Rust environment uses: ./.github/actions/setup with: rust-version: stable target: ${{ matrix.target }} cache-shared-key: build-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} github-token: ${{ secrets.GITHUB_TOKEN }} cache-save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} install-cross-tools: ${{ matrix.cross }} - name: Download static console assets run: | mkdir -p ./rustfs/static if [[ "${{ matrix.platform }}" == "windows" ]]; then curl.exe -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" -o console.zip --retry 3 --retry-delay 5 --max-time 300 if [[ $? -eq 0 ]]; then unzip -o console.zip -d ./rustfs/static rm console.zip else echo "Warning: Failed to download console assets, continuing without them" echo "// Static assets not available" > ./rustfs/static/empty.txt fi else curl -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" \ -o console.zip --retry 3 --retry-delay 5 --max-time 300 if [[ $? -eq 0 ]]; then unzip -o console.zip -d ./rustfs/static rm console.zip else echo "Warning: Failed to download console assets, continuing without them" echo "// Static assets not available" > ./rustfs/static/empty.txt fi fi - name: Build RustFS run: | # Force rebuild by touching build.rs touch rustfs/build.rs if [[ "${{ matrix.cross }}" == "true" ]]; then if [[ "${{ matrix.platform }}" == "windows" ]]; then # Use cross for Windows ARM64 cargo install cross --git https://github.com/cross-rs/cross cross build --release --target ${{ matrix.target }} -p rustfs --bins else # Use zigbuild for Linux ARM64 cargo zigbuild --release --target ${{ matrix.target }} -p rustfs --bins fi else cargo build --release --target ${{ matrix.target }} -p rustfs --bins fi - name: Create release package id: package shell: bash run: | PACKAGE_NAME="rustfs-${{ matrix.target }}" mkdir -p "${PACKAGE_NAME}"/{bin,docs} # Copy binary with platform-specific handling if [[ "${{ matrix.target }}" == *"windows"* ]]; then cp target/${{ matrix.target }}/release/rustfs.exe "${PACKAGE_NAME}/bin/" # Add Windows-specific files echo "RustFS for Windows" > "${PACKAGE_NAME}/README.txt" else cp target/${{ matrix.target }}/release/rustfs "${PACKAGE_NAME}/bin/" chmod +x "${PACKAGE_NAME}/bin/rustfs" fi # Copy documentation [ -f "LICENSE" ] && cp LICENSE "${PACKAGE_NAME}/docs/" [ -f "README.md" ] && cp README.md "${PACKAGE_NAME}/docs/" # Add platform-specific information cat > "${PACKAGE_NAME}/docs/PLATFORM_INFO.txt" << EOF Platform: ${{ matrix.platform }} Target: ${{ matrix.target }} Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC") Git Commit: ${GITHUB_SHA::8} EOF # Create appropriate archive format for platform if [[ "${{ matrix.platform }}" == "windows" ]]; then # Use PowerShell to create zip on Windows powershell -Command "Compress-Archive -Path '${PACKAGE_NAME}' -DestinationPath '${PACKAGE_NAME}.zip'" echo "package_name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT echo "package_file=${PACKAGE_NAME}.zip" >> $GITHUB_OUTPUT echo "Package created: ${PACKAGE_NAME}.zip" else tar -czf "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}" echo "package_name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT echo "package_file=${PACKAGE_NAME}.tar.gz" >> $GITHUB_OUTPUT echo "Package created: ${PACKAGE_NAME}.tar.gz" fi - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: ${{ steps.package.outputs.package_name }} path: ${{ steps.package.outputs.package_file }} retention-days: ${{ startsWith(github.ref, 'refs/tags/') && 30 || 7 }} # Build GUI (only for releases) build-gui: name: Build GUI needs: [build-check, build-rustfs] if: needs.build-check.outputs.build_type == 'release' runs-on: ${{ matrix.os }} timeout-minutes: 45 strategy: fail-fast: false matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-musl platform: linux - os: macos-latest target: aarch64-apple-darwin platform: macos - os: windows-latest target: x86_64-pc-windows-msvc platform: windows steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Rust environment uses: ./.github/actions/setup with: rust-version: stable target: ${{ matrix.target }} cache-shared-key: gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download RustFS binary uses: actions/download-artifact@v4 with: name: rustfs-${{ matrix.target }} path: ./artifacts - name: Prepare embedded binary shell: bash run: | mkdir -p ./cli/rustfs-gui/embedded-rustfs/ if [[ "${{ matrix.platform }}" == "windows" ]]; then # Extract from zip file unzip ./artifacts/rustfs-${{ matrix.target }}.zip -d ./artifacts/ cp ./artifacts/rustfs-${{ matrix.target }}/bin/rustfs.exe ./cli/rustfs-gui/embedded-rustfs/ else # Extract from tar.gz file tar -xzf ./artifacts/rustfs-${{ matrix.target }}.tar.gz -C ./artifacts/ cp ./artifacts/rustfs-${{ matrix.target }}/bin/rustfs ./cli/rustfs-gui/embedded-rustfs/ fi - name: Install Dioxus CLI uses: taiki-e/cache-cargo-install-action@v2 with: tool: dioxus-cli - name: Build GUI working-directory: ./cli/rustfs-gui shell: bash run: | case "${{ matrix.platform }}" in "linux") dx bundle --platform linux --package-types deb --package-types appimage --release ;; "macos") dx bundle --platform macos --package-types dmg --release ;; "windows") dx bundle --platform windows --package-types msi --release ;; esac - name: Package GUI id: gui_package shell: bash run: | GUI_PACKAGE="rustfs-gui-${{ matrix.target }}" mkdir -p "${GUI_PACKAGE}" # Copy GUI bundles if [[ -d "cli/rustfs-gui/dist/bundle" ]]; then cp -r cli/rustfs-gui/dist/bundle/* "${GUI_PACKAGE}/" fi # Create appropriate archive format if [[ "${{ matrix.platform }}" == "windows" ]]; then powershell -Command "Compress-Archive -Path '${GUI_PACKAGE}' -DestinationPath '${GUI_PACKAGE}.zip'" echo "gui_package=${GUI_PACKAGE}" >> $GITHUB_OUTPUT echo "gui_file=${GUI_PACKAGE}.zip" >> $GITHUB_OUTPUT else tar -czf "${GUI_PACKAGE}.tar.gz" "${GUI_PACKAGE}" echo "gui_package=${GUI_PACKAGE}" >> $GITHUB_OUTPUT echo "gui_file=${GUI_PACKAGE}.tar.gz" >> $GITHUB_OUTPUT fi - name: Upload GUI artifacts uses: actions/upload-artifact@v4 with: name: ${{ steps.gui_package.outputs.gui_package }} path: ${{ steps.gui_package.outputs.gui_file }} retention-days: 30 # Release management release: name: GitHub Release needs: [build-check, build-rustfs, build-gui] if: always() && needs.build-check.outputs.build_type == 'release' runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: ./release-artifacts - name: Prepare release assets id: release_prep run: | VERSION="${GITHUB_REF#refs/tags/}" VERSION_CLEAN="${VERSION#v}" echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version_clean=${VERSION_CLEAN}" >> $GITHUB_OUTPUT # Organize artifacts mkdir -p ./release-files # Copy all artifacts (both .tar.gz and .zip files) find ./release-artifacts -name "*.tar.gz" -exec cp {} ./release-files/ \; find ./release-artifacts -name "*.zip" -exec cp {} ./release-files/ \; # Generate checksums for all files cd ./release-files if ls *.tar.gz >/dev/null 2>&1; then sha256sum *.tar.gz >> SHA256SUMS sha512sum *.tar.gz >> SHA512SUMS fi if ls *.zip >/dev/null 2>&1; then sha256sum *.zip >> SHA256SUMS sha512sum *.zip >> SHA512SUMS fi cd .. # Display what we're releasing echo "=== Release Files ===" ls -la ./release-files/ - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" VERSION_CLEAN="${{ steps.release_prep.outputs.version_clean }}" # Check if release already exists if gh release view "$VERSION" >/dev/null 2>&1; then echo "Release $VERSION already exists, skipping creation" else # Get release notes from tag message RELEASE_NOTES=$(git tag -l --format='%(contents)' "${VERSION}") if [[ -z "$RELEASE_NOTES" || "$RELEASE_NOTES" =~ ^[[:space:]]*$ ]]; then RELEASE_NOTES="Release ${VERSION_CLEAN}" fi # Determine if this is a prerelease PRERELEASE_FLAG="" if [[ "$VERSION" == *"alpha"* ]] || [[ "$VERSION" == *"beta"* ]] || [[ "$VERSION" == *"rc"* ]]; then PRERELEASE_FLAG="--prerelease" fi # Create the release only if it doesn't exist gh release create "$VERSION" \ --title "RustFS $VERSION_CLEAN" \ --notes "$RELEASE_NOTES" \ $PRERELEASE_FLAG fi - name: Upload release assets env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" cd ./release-files # Upload all binary archives for file in *.tar.gz *.zip; do if [[ -f "$file" ]]; then echo "Uploading $file..." gh release upload "$VERSION" "$file" --clobber fi done # Upload checksum files if [[ -f "SHA256SUMS" ]]; then echo "Uploading SHA256SUMS..." gh release upload "$VERSION" "SHA256SUMS" --clobber fi if [[ -f "SHA512SUMS" ]]; then echo "Uploading SHA512SUMS..." gh release upload "$VERSION" "SHA512SUMS" --clobber fi - name: Update release notes env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" VERSION_CLEAN="${{ steps.release_prep.outputs.version_clean }}" # Check if release already has custom notes (not auto-generated) EXISTING_NOTES=$(gh release view "$VERSION" --json body --jq '.body' 2>/dev/null || echo "") # Only update if release notes are empty or auto-generated if [[ -z "$EXISTING_NOTES" ]] || [[ "$EXISTING_NOTES" == *"Release ${VERSION_CLEAN}"* ]]; then echo "Updating release notes for $VERSION" # Get original release notes from tag ORIGINAL_NOTES=$(git tag -l --format='%(contents)' "${VERSION}") if [[ -z "$ORIGINAL_NOTES" || "$ORIGINAL_NOTES" =~ ^[[:space:]]*$ ]]; then ORIGINAL_NOTES="Release ${VERSION_CLEAN}" fi # Create comprehensive release notes cat > enhanced_notes.md << EOF ## RustFS ${VERSION_CLEAN} ${ORIGINAL_NOTES} --- ### 🚀 Quick Download **Linux (Recommended - Static Binaries):** \`\`\`bash # x86_64 (Intel/AMD) curl -LO https://github.com/rustfs/rustfs/releases/download/${VERSION}/rustfs-x86_64-unknown-linux-musl.tar.gz tar -xzf rustfs-x86_64-unknown-linux-musl.tar.gz sudo cp rustfs-x86_64-unknown-linux-musl/bin/rustfs /usr/local/bin/ # ARM64 (Graviton, Apple Silicon VMs) curl -LO https://github.com/rustfs/rustfs/releases/download/${VERSION}/rustfs-aarch64-unknown-linux-musl.tar.gz tar -xzf rustfs-aarch64-unknown-linux-musl.tar.gz sudo cp rustfs-aarch64-unknown-linux-musl/bin/rustfs /usr/local/bin/ \`\`\` **macOS:** \`\`\`bash # Apple Silicon (M1/M2/M3) curl -LO https://github.com/rustfs/rustfs/releases/download/${VERSION}/rustfs-aarch64-apple-darwin.tar.gz tar -xzf rustfs-aarch64-apple-darwin.tar.gz sudo cp rustfs-aarch64-apple-darwin/bin/rustfs /usr/local/bin/ # Intel curl -LO https://github.com/rustfs/rustfs/releases/download/${VERSION}/rustfs-x86_64-apple-darwin.tar.gz tar -xzf rustfs-x86_64-apple-darwin.tar.gz sudo cp rustfs-x86_64-apple-darwin/bin/rustfs /usr/local/bin/ \`\`\` **Windows:** \`\`\`powershell # Download and extract Invoke-WebRequest https://github.com/rustfs/rustfs/releases/download/${VERSION}/rustfs-x86_64-pc-windows-msvc.zip -OutFile rustfs.zip Expand-Archive rustfs.zip \`\`\` ### 📁 Available Downloads | Platform | Architecture | File | Size | |----------|-------------|------|------| | Linux | x86_64 | \`rustfs-x86_64-unknown-linux-musl.tar.gz\` | Static binary | | Linux | ARM64 | \`rustfs-aarch64-unknown-linux-musl.tar.gz\` | Static binary | | macOS | Apple Silicon | \`rustfs-aarch64-apple-darwin.tar.gz\` | Native binary | | macOS | Intel | \`rustfs-x86_64-apple-darwin.tar.gz\` | Native binary | | Windows | x86_64 | \`rustfs-x86_64-pc-windows-msvc.zip\` | MSVC binary | | Windows | ARM64 | \`rustfs-aarch64-pc-windows-msvc.zip\` | Experimental | ### 🔐 Verification Download checksums and verify your download: \`\`\`bash # Download checksums curl -LO https://github.com/rustfs/rustfs/releases/download/${VERSION}/SHA256SUMS # Verify (Linux/macOS) sha256sum -c SHA256SUMS --ignore-missing # Verify (Windows PowerShell) \$expectedHash = (Get-Content SHA256SUMS | Select-String "rustfs-x86_64-pc-windows-msvc.zip").Line.Split()[0] \$actualHash = (Get-FileHash rustfs-x86_64-pc-windows-msvc.zip -Algorithm SHA256).Hash.ToLower() if (\$expectedHash -eq \$actualHash) { "✅ Checksum verified" } else { "❌ Checksum mismatch" } \`\`\` ### 🛠️ System Requirements - **Linux**: Any distribution with glibc 2.17+ (CentOS 7+, Ubuntu 16.04+) - **macOS**: 10.15+ (Catalina or later) - **Windows**: Windows 10 version 1809 or later ### 📚 Documentation - [Installation Guide](https://github.com/rustfs/rustfs#installation) - [Quick Start](https://github.com/rustfs/rustfs#quick-start) - [Configuration](https://github.com/rustfs/rustfs/blob/main/docs/) - [API Documentation](https://docs.rs/rustfs) ### 🆘 Support - 🐛 [Report Issues](https://github.com/rustfs/rustfs/issues) - 💬 [Community Discussions](https://github.com/rustfs/rustfs/discussions) - 📖 [Documentation](https://github.com/rustfs/rustfs/tree/main/docs) EOF # Update the release with enhanced notes gh release edit "$VERSION" --notes-file enhanced_notes.md else echo "Release $VERSION already has custom notes, skipping update to preserve manual edits" fi # Upload to OSS (optional) upload-oss: name: Upload to OSS needs: [build-check, build-rustfs] if: always() && needs.build-check.outputs.build_type == 'release' && needs.build-rustfs.result == 'success' runs-on: ubuntu-latest env: OSS_ACCESS_KEY_ID: ${{ secrets.ALICLOUDOSS_KEY_ID }} OSS_ACCESS_KEY_SECRET: ${{ secrets.ALICLOUDOSS_KEY_SECRET }} steps: - name: Download artifacts uses: actions/download-artifact@v4 with: path: ./artifacts - name: Upload to Aliyun OSS if: ${{ env.OSS_ACCESS_KEY_ID != '' }} run: | # Install ossutil curl -o ossutil.zip https://gosspublic.alicdn.com/ossutil/v2/2.1.1/ossutil-2.1.1-linux-amd64.zip unzip ossutil.zip sudo mv ossutil-*/ossutil /usr/local/bin/ # Upload files (both .tar.gz and .zip) find ./artifacts -name "*.tar.gz" -exec ossutil cp {} oss://rustfs-artifacts/artifacts/rustfs/ --force \; find ./artifacts -name "*.zip" -exec ossutil cp {} oss://rustfs-artifacts/artifacts/rustfs/ --force \; # Create latest.json VERSION="${GITHUB_REF#refs/tags/v}" echo "{\"version\":\"${VERSION}\",\"release_date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > latest.json ossutil cp latest.json oss://rustfs-version/latest.json --force