Skip to content

Merge pull request #18 from Pikatsuto/devlop #157

Merge pull request #18 from Pikatsuto/devlop

Merge pull request #18 from Pikatsuto/devlop #157

Workflow file for this run

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 }}