Merge pull request #18 from Pikatsuto/devlop #157
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 Raspberry Pi Images | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - test | |
| - preview | |
| paths: | |
| - '.github/workflows/build-images.yml' | |
| - '.github/images.txt' | |
| - 'images/**' | |
| - 'bin/**' | |
| workflow_dispatch: | |
| # Allows manual trigger | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| jobs: | |
| # Job 1: Detect available images | |
| detect-images: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| images: ${{ steps.list.outputs.images }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: List available images from .github/images.txt | |
| id: list | |
| run: | | |
| # Read images from .github/images.txt, filter out comments and empty lines | |
| images=$(grep -v '^#' .github/images.txt | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]') | |
| echo "images=$images" >> $GITHUB_OUTPUT | |
| echo "Images to build:" | |
| grep -v '^#' .github/images.txt | grep -v '^$' | |
| # Job 2: Stage 1+2 - Download and QEMU Setup (parallel for all images) | |
| stage1-2-download-qemu: | |
| needs: detect-images | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| image: ${{ fromJson(needs.detect-images.outputs.images) }} | |
| fail-fast: false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Generate sanitized artifact name | |
| id: artifact | |
| run: | | |
| # Replace / and + with - for artifact names | |
| SANITIZED=$(echo "${{ matrix.image }}" | tr '/+' '--') | |
| echo "name=${SANITIZED}" >> $GITHUB_OUTPUT | |
| echo "Artifact name: ${SANITIZED}" | |
| - name: Free disk space | |
| run: | | |
| # Free up disk space for large images | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo docker image prune --all --force | |
| df -h | |
| - name: Install dependencies | |
| run: | | |
| sudo apt update | |
| sudo apt install -y \ | |
| wget \ | |
| genisoimage \ | |
| xz-utils \ | |
| qemu-system-aarch64 \ | |
| qemu-utils \ | |
| qemu-efi-aarch64 | |
| - name: Stage 1 - Download and prepare for ${{ matrix.image }} | |
| run: | | |
| ./bin/autobuild --image "${{ matrix.image }}" --stage 1 | |
| - name: Stage 2 - QEMU setup for ${{ matrix.image }} | |
| run: | | |
| ./bin/autobuild --image "${{ matrix.image }}" --stage 2 | |
| timeout-minutes: 60 | |
| - name: Clean up unnecessary files before upload | |
| run: | | |
| # Keep only the files needed for stage 3 | |
| echo "Files before cleanup:" | |
| ls -lh *.img *.raw 2>/dev/null || true | |
| # Remove setup.iso if it exists (not needed after stage 2) | |
| rm -f setup.iso | |
| # Remove downloaded distribution image (already copied to distro-arm64.raw) | |
| # Keep only RaspiOS .img and distro-arm64.raw | |
| find . -maxdepth 1 -name "*.raw" ! -name "distro-arm64.raw" -delete | |
| find . -maxdepth 1 -name "*.qcow2" -delete | |
| echo "" | |
| echo "Files after cleanup (ready for upload):" | |
| ls -lh *.img distro-arm64.raw 2>/dev/null || true | |
| - name: Upload configured distro and RaspiOS images | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: images-ready-${{ steps.artifact.outputs.name }} | |
| path: | | |
| *raspios*.img | |
| distro-arm64.raw | |
| retention-days: 1 | |
| compression-level: 0 | |
| # Job 3: Create Release (runs in parallel with build stages) | |
| create-release: | |
| needs: detect-images | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| tag: ${{ steps.release_info.outputs.tag }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Fetch all history for changelog generation | |
| - name: Determine build type | |
| id: build_type | |
| run: | | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| echo "type=cron" >> $GITHUB_OUTPUT | |
| echo "🕐 Cron build detected - will create/update daily release" | |
| elif [ "${{ github.event_name }}" = "push" ]; then | |
| echo "type=push" >> $GITHUB_OUTPUT | |
| echo "📝 Push build detected - will create new release with changelog" | |
| else | |
| echo "type=manual" >> $GITHUB_OUTPUT | |
| echo "🔧 Manual build detected - will create new release" | |
| fi | |
| - name: Generate release name and tag | |
| id: release_info | |
| run: | | |
| RELEASE_DATE=$(date +'%Y-%m-%d') | |
| RELEASE_TIME=$(date +'%H%M') | |
| # Determine if it's a pre-release | |
| if [ "${{ github.ref_name }}" = "main" ]; then | |
| IS_PRERELEASE="false" | |
| else | |
| IS_PRERELEASE="true" | |
| fi | |
| # For cron builds, use fixed daily tag | |
| if [ "${{ steps.build_type.outputs.type }}" = "cron" ]; then | |
| if [ "$IS_PRERELEASE" = "true" ]; then | |
| RELEASE_TAG="daily-${RELEASE_DATE}-${{ github.ref_name }}" | |
| RELEASE_NAME="Daily Build ${RELEASE_DATE} (${{ github.ref_name }})" | |
| else | |
| RELEASE_TAG="daily-${RELEASE_DATE}" | |
| RELEASE_NAME="Daily Build ${RELEASE_DATE}" | |
| fi | |
| else | |
| # For push/manual builds, use timestamp tag | |
| if [ "$IS_PRERELEASE" = "false" ]; then | |
| RELEASE_TAG="v${RELEASE_DATE}-${RELEASE_TIME}" | |
| RELEASE_NAME="Release ${RELEASE_DATE}" | |
| else | |
| RELEASE_TAG="v${RELEASE_DATE}-${RELEASE_TIME}-${{ github.ref_name }}" | |
| RELEASE_NAME="Pre-release ${RELEASE_DATE} (${{ github.ref_name }})" | |
| fi | |
| fi | |
| echo "tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT | |
| echo "name=${RELEASE_NAME}" >> $GITHUB_OUTPUT | |
| echo "prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT | |
| echo "date=${RELEASE_DATE}" >> $GITHUB_OUTPUT | |
| echo "time=${RELEASE_TIME}" >> $GITHUB_OUTPUT | |
| - name: Generate changelog (for push builds) | |
| if: steps.build_type.outputs.type == 'push' | |
| id: changelog | |
| run: | | |
| # Get the last release tag | |
| LAST_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") | |
| if [ -z "$LAST_TAG" ]; then | |
| echo "No previous release found, generating full changelog" | |
| CHANGELOG=$(git log --pretty=format:"- %s (%h) - %an" --no-merges) | |
| else | |
| echo "Generating changelog since $LAST_TAG" | |
| CHANGELOG=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s (%h) - %an" --no-merges) | |
| fi | |
| # Save changelog to file | |
| if [ -n "$CHANGELOG" ]; then | |
| echo "### 📝 Changelog" > changelog.md | |
| echo "" >> changelog.md | |
| echo "$CHANGELOG" >> changelog.md | |
| echo "" >> changelog.md | |
| else | |
| echo "### 📝 Changelog" > changelog.md | |
| echo "" >> changelog.md | |
| echo "No new commits since last release." >> changelog.md | |
| echo "" >> changelog.md | |
| fi | |
| echo "Changelog generated:" | |
| cat changelog.md | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create release body (push builds) | |
| if: steps.build_type.outputs.type == 'push' | |
| run: | | |
| # Determine branch-specific header and warnings | |
| if [ "${{ github.ref_name }}" = "main" ]; then | |
| cat > release_body.md << 'EOF' | |
| ## 🚀 Raspberry Pi Hybrid Images - ${{ steps.release_info.outputs.date }} | |
| Raspberry Pi images with RaspiOS boot + Debian ARM64 rootfs. | |
| ### 📦 Available Images | |
| Images are being built and will be uploaded as they complete. | |
| EOF | |
| elif [ "${{ github.ref_name }}" = "test" ]; then | |
| cat > release_body.md << 'EOF' | |
| ## 🧪 TEST BUILD - Raspberry Pi Hybrid Images - ${{ steps.release_info.outputs.date }} | |
| > **⚠️ WARNING - TEST BUILD** | |
| > | |
| > These images are from the `test` branch and are for testing purposes only. | |
| > **NO GUARANTEE of functionality is provided.** | |
| > | |
| > - Do not use in production | |
| > - May contain critical bugs | |
| > - May not boot properly | |
| > - Use at your own risk | |
| > | |
| > For stable images, check releases from the `main` branch. | |
| Raspberry Pi images with RaspiOS boot + Debian ARM64 rootfs. | |
| ### 📦 Available Images | |
| Images are being built and will be uploaded as they complete. | |
| EOF | |
| elif [ "${{ github.ref_name }}" = "preview" ]; then | |
| cat > release_body.md << 'EOF' | |
| ## 🔍 PREVIEW BUILD - Raspberry Pi Hybrid Images - ${{ steps.release_info.outputs.date }} | |
| > **ℹ️ Preview Build** | |
| > | |
| > These images are from the `preview` branch and are preview versions. | |
| > They may contain experimental or in-development features. | |
| > | |
| > Test at your own risk. For stable versions, check releases from the `main` branch. | |
| Raspberry Pi images with RaspiOS boot + Debian ARM64 rootfs. | |
| ### 📦 Available Images | |
| Images are being built and will be uploaded as they complete. | |
| EOF | |
| fi | |
| # Add changelog if it exists | |
| if [ -f changelog.md ]; then | |
| cat changelog.md >> release_body.md | |
| fi | |
| cat >> release_body.md << 'EOF' | |
| ### 📝 Flash an Image | |
| ```bash | |
| # Decompress and flash | |
| xz -dc rpi-*.img.xz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync | |
| sync | |
| ``` | |
| **⚠️ Replace `/dev/sdX` with your device!** | |
| ### 🔧 Build Info | |
| - **Branch** : `${{ github.ref_name }}` | |
| - **Commit** : `${{ github.sha }}` | |
| - **Date** : `${{ steps.release_info.outputs.date }}` | |
| - **Workflow** : [Build #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| ### 📚 Documentation | |
| See [README.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/README.md) for more information. | |
| EOF | |
| - name: Create release body (cron/manual builds) | |
| if: steps.build_type.outputs.type != 'push' | |
| run: | | |
| cat > release_body.md << 'EOF' | |
| ## 🚀 Raspberry Pi Hybrid Images - Daily Build | |
| Raspberry Pi images with RaspiOS boot + Debian ARM64 rootfs. | |
| **🕐 Build started:** `${{ steps.release_info.outputs.date }}` at `${{ steps.release_info.outputs.time }}` | |
| ### 📦 Available Images | |
| Images are being built and will be uploaded as they complete. | |
| ### 📝 Flash an Image | |
| ```bash | |
| # Decompress and flash | |
| xz -dc rpi-*.img.xz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync | |
| sync | |
| ``` | |
| **⚠️ Replace `/dev/sdX` with your device!** | |
| ### 🔧 Build Info | |
| - **Branch** : `${{ github.ref_name }}` | |
| - **Last Commit** : `${{ github.sha }}` | |
| - **Build Date** : `${{ steps.release_info.outputs.date }}` at `${{ steps.release_info.outputs.time }}` | |
| - **Workflow** : [Build #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| ### 📚 Documentation | |
| See [README.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/README.md) for more information. | |
| EOF | |
| - name: Delete existing release if exists (cron builds) | |
| if: steps.build_type.outputs.type == 'cron' | |
| run: | | |
| # Delete existing release if it exists | |
| if gh release view ${{ steps.release_info.outputs.tag }} >/dev/null 2>&1; then | |
| echo "Deleting existing release ${{ steps.release_info.outputs.tag }}..." | |
| gh release delete ${{ steps.release_info.outputs.tag }} --yes | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create release | |
| id: create_release | |
| run: | | |
| PRERELEASE_FLAG="" | |
| if [ "${{ steps.release_info.outputs.prerelease }}" = "true" ]; then | |
| PRERELEASE_FLAG="--prerelease" | |
| fi | |
| gh release create ${{ steps.release_info.outputs.tag }} \ | |
| --title "${{ steps.release_info.outputs.name }}" \ | |
| --notes-file release_body.md \ | |
| $PRERELEASE_FLAG | |
| echo "✅ Release ${{ steps.release_info.outputs.tag }} created successfully" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Job 4: Stage 3+4 - Build hybrid image, compress and upload (parallel for all images) | |
| stage3-4-build-compress: | |
| needs: [detect-images, stage1-2-download-qemu, create-release] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| strategy: | |
| matrix: | |
| image: ${{ fromJson(needs.detect-images.outputs.images) }} | |
| fail-fast: false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Generate sanitized artifact name | |
| id: artifact | |
| run: | | |
| # Replace / and + with - for artifact names | |
| SANITIZED=$(echo "${{ matrix.image }}" | tr '/+' '--') | |
| echo "name=${SANITIZED}" >> $GITHUB_OUTPUT | |
| - name: Install build and compression dependencies | |
| run: | | |
| sudo apt update | |
| sudo apt install -y \ | |
| parted \ | |
| e2fsprogs \ | |
| dosfstools \ | |
| rsync \ | |
| qemu-utils | |
| - name: Install PiShrink | |
| run: | | |
| wget -q https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh | |
| chmod +x pishrink.sh | |
| sudo mv pishrink.sh /usr/local/bin/ | |
| - name: Enable loop devices | |
| run: | | |
| # Load loop module | |
| sudo modprobe loop | |
| # Check available loop devices | |
| ls -la /dev/loop* || true | |
| - name: Download images from stage 1+2 | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: images-ready-${{ steps.artifact.outputs.name }} | |
| - name: Stage 3 - Build hybrid image for ${{ matrix.image }} | |
| run: | | |
| ./bin/autobuild --image "${{ matrix.image }}" --stage 3 | |
| - name: Stage 4 - Compress image for ${{ matrix.image }} | |
| run: | | |
| # Find the hybrid .img file (exclude RaspiOS images) | |
| IMAGE_FILE=$(ls *.img 2>/dev/null | grep -v raspios | head -1) | |
| if [ -z "$IMAGE_FILE" ]; then | |
| echo "ERROR: No hybrid .img file found!" | |
| ls -la | |
| exit 1 | |
| fi | |
| echo "Found image: $IMAGE_FILE" | |
| echo "Compressing $IMAGE_FILE with PiShrink..." | |
| # Compress with PiShrink | |
| sudo pishrink.sh -aZ "$IMAGE_FILE" | |
| echo "Compression complete. Output:" | |
| ls -lh *.img.xz | |
| - name: Upload to release | |
| run: | | |
| # Upload compressed image to release | |
| gh release upload ${{ needs.create-release.outputs.tag }} *.img.xz --clobber | |
| echo "✅ Uploaded $(ls *.img.xz) to release" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| # Clean up large files | |
| sudo rm -f *.img *.img.xz *.raw | |
| df -h | |
| # Job 5: Cleanup empty release (runs after all builds complete or fail) | |
| cleanup-release: | |
| needs: [create-release, stage3-4-build-compress] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Check release and delete if empty | |
| run: | | |
| RELEASE_TAG="${{ needs.create-release.outputs.tag }}" | |
| echo "Checking release $RELEASE_TAG for assets..." | |
| # Get asset count from release | |
| ASSET_COUNT=$(gh release view "$RELEASE_TAG" --json assets --jq '.assets | length' 2>/dev/null || echo "0") | |
| echo "Release has $ASSET_COUNT asset(s)" | |
| if [ "$ASSET_COUNT" -eq 0 ]; then | |
| echo "⚠️ Release $RELEASE_TAG is empty (no assets uploaded)" | |
| echo "Deleting empty release..." | |
| gh release delete "$RELEASE_TAG" --yes | |
| echo "✅ Empty release deleted successfully" | |
| else | |
| echo "✅ Release $RELEASE_TAG has $ASSET_COUNT asset(s), keeping it" | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |