Weekly Auto Build FAP for Release and Dev Channels #9
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: Weekly Auto Build FAP for Release and Dev Channels | |
on: | |
schedule: | |
# Run every Sunday at 12:00 UTC | |
- cron: '0 12 * * 0' | |
workflow_dispatch: | |
inputs: | |
force_build: | |
description: 'Force build for release and dev channels' | |
required: false | |
default: false | |
type: boolean | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
strategy: | |
fail-fast: false # Prevents one failing job from canceling others | |
matrix: | |
channel: | |
- release | |
- dev | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: '3.11' | |
- name: Install ufbt | |
run: | | |
python -m pip install --upgrade pip | |
pip install ufbt | |
ufbt --version | |
python --version | |
echo "Working directory: $(pwd)" | |
- name: Setup ufbt | |
run: | | |
echo "Setting up ufbt for ${{ matrix.channel }} channel..." | |
ufbt update --channel ${{ matrix.channel }} || { | |
echo "::error::ufbt update failed for ${{ matrix.channel }} channel" | |
exit 1 | |
} | |
echo "ufbt status:" | |
ufbt status | |
echo "ufbt build directory:" | |
ls -la /home/runner/.ufbt/build/ 2>/dev/null || echo "Build directory not found" | |
- name: Pre-build validation | |
run: | | |
echo "Validating build environment for ${{ matrix.channel }} channel..." | |
echo "Checking for application.fam..." | |
if [ -f "application.fam" ]; then | |
echo "application.fam found:" | |
cat application.fam | |
else | |
echo "::error::No application.fam found in $(pwd)" | |
exit 1 | |
fi | |
echo "Listing source files (*.c, *.h)..." | |
ls -la *.c *.h 2>/dev/null || echo "::warning::No .c or .h files found" | |
echo "Checking assets directory..." | |
if [ -d "assets" ]; then | |
echo "assets directory found:" | |
ls -la assets/ | |
else | |
echo "::warning::No assets directory found" | |
fi | |
echo "Checking icons directory..." | |
if [ -d "icons" ]; then | |
echo "icons directory found:" | |
ls -la icons/ | |
if [ -f "icons/jammer_icon.png" ]; then | |
echo "jammer_icon.png found" | |
else | |
echo "::warning::jammer_icon.png not found in icons/" | |
fi | |
else | |
echo "::warning::No icons directory found" | |
fi | |
echo "Checking dist directory..." | |
mkdir -p dist | |
ls -la dist/ 2>/dev/null || echo "dist directory is empty" | |
- name: Get app name from source | |
id: app-info | |
run: | | |
# Try to extract app name from common patterns | |
if [ -f "application.fam" ]; then | |
APP_NAME=$(grep -E "name\s*=" application.fam | sed 's/.*name\s*=\s*"\([^"]*\)".*/\1/' | head -1) | |
elif ls *.c 2>/dev/null; then | |
# Look for app name in C files | |
APP_NAME=$(grep -r "\.name.*=" *.c | sed 's/.*\.name.*=.*"\([^"]*\)".*/\1/' | head -1) | |
fi | |
# Fallback to directory name | |
if [ -z "$APP_NAME" ]; then | |
APP_NAME=$(basename "$(pwd)") | |
fi | |
# Clean up name for filename | |
APP_NAME_CLEAN=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g') | |
echo "name=$APP_NAME" >> $GITHUB_OUTPUT | |
echo "clean_name=$APP_NAME_CLEAN" >> $GITHUB_OUTPUT | |
echo "App name: $APP_NAME" | |
- name: Build FAP | |
run: | | |
echo "Building FAP for ${{ matrix.channel }} channel..." | |
# Run ufbt build and capture output to a log file | |
mkdir -p build_logs | |
BUILD_LOG="build_logs/ufbt_build_${{ matrix.channel }}.log" | |
ufbt build 2>&1 | tee "$BUILD_LOG" | |
BUILD_EXIT_CODE=${PIPESTATUS[0]} | |
if [ $BUILD_EXIT_CODE -ne 0 ]; then | |
echo "::error::ufbt build failed for ${{ matrix.channel }} channel (exit code $BUILD_EXIT_CODE)" | |
echo "Build log contents:" | |
cat "$BUILD_LOG" | |
echo "ufbt status:" | |
ufbt status | |
echo "Directory contents:" | |
ls -la | |
echo "dist directory contents:" | |
ls -la dist/ 2>/dev/null || echo "dist directory is empty" | |
echo "ufbt build directory contents:" | |
ls -la /home/runner/.ufbt/build/ 2>/dev/null || echo "ufbt build directory is empty" | |
echo "application.fam contents:" | |
cat application.fam 2>/dev/null || echo "No application.fam found" | |
exit 1 | |
fi | |
# Check ufbt build directory for FAP file | |
echo "Checking ufbt build directory for FAP file..." | |
FAP_FILE_UFBT=$(find /home/runner/.ufbt/build/ -name "*.fap" | head -1) | |
if [ -n "$FAP_FILE_UFBT" ]; then | |
echo "FAP file found in ufbt build directory: $FAP_FILE_UFBT" | |
mkdir -p dist | |
cp "$FAP_FILE_UFBT" dist/jammer_app.fap | |
echo "Copied FAP file to dist/jammer_app.fap" | |
fi | |
# Check dist directory for FAP file | |
FAP_FILE=$(find . -name "*.fap" -path "*/dist/*" | head -1) | |
if [ -z "$FAP_FILE" ]; then | |
echo "::error::No .fap file generated in dist/ for ${{ matrix.channel }} channel" | |
echo "dist directory contents:" | |
ls -la dist/ 2>/dev/null || echo "dist directory is empty" | |
echo "ufbt build directory contents:" | |
ls -la /home/runner/.ufbt/build/ 2>/dev/null || echo "ufbt build directory is empty" | |
exit 1 | |
fi | |
- name: Get current timestamp | |
id: timestamp | |
run: | | |
TIMESTAMP=$(date -u +%Y%m%d_%H%M%S) | |
echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT | |
- name: Find and rename FAP | |
id: fap-info | |
run: | | |
FAP_FILE=$(find . -name "*.fap" -path "*/dist/*" | head -1) | |
if [ -z "$FAP_FILE" ]; then | |
echo "::error::No FAP file found in dist/ for ${{ matrix.channel }} channel" | |
exit 1 | |
fi | |
NEW_NAME="${{ steps.app-info.outputs.clean_name }}-${{ matrix.channel }}-${{ steps.timestamp.outputs.timestamp }}.fap" | |
cp "$FAP_FILE" "$NEW_NAME" | |
echo "filename=$NEW_NAME" >> $GITHUB_OUTPUT | |
- name: Upload artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.channel }}-${{ steps.timestamp.outputs.timestamp }} | |
path: ${{ steps.fap-info.outputs.filename }} | |
create-releases: | |
needs: build | |
runs-on: ubuntu-latest | |
strategy: | |
fail-fast: false | |
matrix: | |
channel: | |
- release | |
- dev | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Get current timestamp | |
id: timestamp | |
run: | | |
TIMESTAMP=$(date -u +%Y%m%d_%H%M%S) | |
echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT | |
- name: Download artifact | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ matrix.channel }}-${{ steps.timestamp.outputs.timestamp }} | |
path: ./release-files | |
- name: Get app info | |
id: app-info | |
run: | | |
if [ -f "application.fam" ]; then | |
APP_NAME=$(grep -E "name\s*=" application.fam | sed 's/.*name\s*=\s*"\([^"]*\)".*/\1/' | head -1) | |
else | |
APP_NAME=$(basename "$(pwd)") | |
fi | |
echo "name=$APP_NAME" >> $GITHUB_OUTPUT | |
- name: Create Release | |
uses: softprops/action-gh-release@v1 | |
with: | |
tag_name: ${{ matrix.channel }}-${{ steps.timestamp.outputs.timestamp }} | |
name: ${{ steps.app-info.outputs.name }} for ${{ matrix.channel }} (${{ steps.timestamp.outputs.timestamp }}) | |
files: ./release-files/*.fap | |
body: | | |
## ${{ steps.app-info.outputs.name }} | |
**Built for:** ${{ matrix.channel }} channel (compatible with official, momentum, unleashed firmwares) | |
This FAP was automatically built on ${{ steps.timestamp.outputs.timestamp }}. | |
**Installation:** | |
1. Download the .fap file | |
2. Copy to your Flipper Zero's `apps` folder | |
3. The app will appear in your applications menu | |
**Firmware Compatibility:** | |
- ✅ Official, Momentum, Unleashed firmwares (${{ matrix.channel }} channel, latest at build time) | |
- ❌ Other channels (use the appropriate release) | |
**Channel Info:** | |
- **Release**: Stable, tested firmware | |
- **Dev**: Latest features, may be unstable | |
draft: false | |
prerelease: ${{ matrix.channel == 'dev' }} | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
cleanup-old-releases: | |
needs: [build, create-releases] | |
runs-on: ubuntu-latest | |
steps: | |
- name: Cleanup releases older than 10 weeks | |
run: | | |
# Calculate the cutoff date (70 days ago) | |
CUTOFF_DATE=$(date -u -d "70 days ago" +%s) | |
echo "Cutoff timestamp: $CUTOFF_DATE" | |
# Fetch all releases | |
RELEASES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
https://api.github.com/repos/${{ github.repository }}/releases) | |
# Filter releases older than 70 days | |
OLD_RELEASES=$(echo "$RELEASES" | jq -r --argjson cutoff "$CUTOFF_DATE" \ | |
'.[] | select((.created_at | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601) < $cutoff) | .id') | |
for release_id in $OLD_RELEASES; do | |
echo "Deleting old release: $release_id" | |
curl -X DELETE -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
"https://api.github.com/repos/${{ github.repository }}/releases/$release_id" | |
done |