All checks were successful
Build and Release to F-Droid / build-and-deploy (push) Successful in 11m33s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
224 lines
9.0 KiB
YAML
224 lines
9.0 KiB
YAML
name: Build and Release to F-Droid
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- '*'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
runs-on: docker
|
|
env:
|
|
ANDROID_HOME: /opt/android-sdk
|
|
ANDROID_SDK_ROOT: /opt/android-sdk
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Java
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: 'zulu'
|
|
java-version: '17'
|
|
|
|
- name: Setup Android SDK
|
|
uses: android-actions/setup-android@v3
|
|
|
|
- name: Install Android SDK packages
|
|
run: |
|
|
sdkmanager --licenses >/dev/null <<'EOF'
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
y
|
|
EOF
|
|
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
|
|
|
|
- name: Install jq
|
|
run: |
|
|
set -e
|
|
SUDO=""
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
SUDO="sudo"
|
|
fi
|
|
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
$SUDO apt-get update
|
|
$SUDO apt-get install -y jq
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
$SUDO apk add --no-cache jq
|
|
elif command -v dnf >/dev/null 2>&1; then
|
|
$SUDO dnf install -y jq
|
|
elif command -v yum >/dev/null 2>&1; then
|
|
$SUDO yum install -y jq
|
|
else
|
|
echo "Could not find a supported package manager to install jq"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@v2
|
|
with:
|
|
channel: 'stable'
|
|
|
|
- name: Trust Flutter SDK git directory
|
|
run: |
|
|
set -e
|
|
FLUTTER_BIN_DIR="$(dirname "$(command -v flutter)")"
|
|
FLUTTER_SDK_DIR="$(cd "$FLUTTER_BIN_DIR/.." && pwd -P)"
|
|
git config --global --add safe.directory "$FLUTTER_SDK_DIR"
|
|
if [ -n "${FLUTTER_ROOT:-}" ]; then
|
|
git config --global --add safe.directory "$FLUTTER_ROOT"
|
|
fi
|
|
# Runner-specific fallback observed in failing logs
|
|
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.4-x64 || true
|
|
|
|
- name: Verify Android + Flutter toolchain
|
|
run: flutter doctor -v
|
|
|
|
- name: Install dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Set version from git tag
|
|
run: |
|
|
set -e
|
|
RAW_TAG="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
|
|
|
|
# Strip leading 'v' if present (v1.2.3 -> 1.2.3)
|
|
VERSION="${RAW_TAG#v}"
|
|
|
|
# Extract a numeric build number for versionCode.
|
|
# Converts semver x.y.z into a single integer: x*10000 + y*100 + z
|
|
# e.g. 1.1.1 -> 10101, 2.3.15 -> 20315
|
|
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
|
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
|
PATCH=$(echo "$VERSION" | cut -d. -f3)
|
|
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
|
|
VERSION_CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
|
|
|
echo "Version: $VERSION, VersionCode: $VERSION_CODE"
|
|
|
|
# Update pubspec.yaml: replace the version line
|
|
sed -i "s/^version: .*/version: ${VERSION}+${VERSION_CODE}/" pubspec.yaml
|
|
|
|
grep '^version:' pubspec.yaml
|
|
|
|
# ADD THIS NEW STEP
|
|
- name: Setup Android Keystore
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
run: |
|
|
# Decode the base64 string back into the binary .jks file
|
|
echo "$KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks
|
|
|
|
# Create the key.properties file that build.gradle expects
|
|
echo "storePassword=$KEY_PASSWORD" > android/key.properties
|
|
echo "keyPassword=$KEY_PASSWORD" >> android/key.properties
|
|
echo "keyAlias=$KEY_ALIAS" >> android/key.properties
|
|
echo "storeFile=upload-keystore.jks" >> android/key.properties
|
|
|
|
- name: Build APK
|
|
run: flutter build apk --release
|
|
|
|
- name: Setup F-Droid Server Tools
|
|
run: |
|
|
SUDO=""
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
SUDO="sudo"
|
|
fi
|
|
$SUDO apt-get update
|
|
# sshpass from apt, fdroidserver via pip to get a newer androguard that
|
|
# can parse modern Flutter/AGP APKs (apt ships fdroidserver 2.2.1 which crashes)
|
|
$SUDO apt-get install -y sshpass python3-pip
|
|
pip3 install --break-system-packages --upgrade fdroidserver
|
|
|
|
- name: Initialize or fetch F-Droid Repository
|
|
env:
|
|
HOST: ${{ secrets.HETZNER_HOST }}
|
|
USER: ${{ secrets.HETZNER_USER }}
|
|
PASS: ${{ secrets.HETZNER_PASS }}
|
|
run: |
|
|
mkdir -p fdroid
|
|
|
|
# Ensure remote path exists (sftp mkdir, ignoring errors if already present).
|
|
sshpass -p "$PASS" sftp -o StrictHostKeyChecking=no "$USER@$HOST" <<'SFTP'
|
|
-mkdir dev
|
|
-mkdir dev/fdroid
|
|
-mkdir dev/fdroid/repo
|
|
SFTP
|
|
|
|
# Try to download the entire fdroid/ directory from Hetzner to keep
|
|
# older APKs, the repo keystore, and config.yml across runs.
|
|
# If it fails (first time), initialize a new local repo.
|
|
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no -r "$USER@$HOST:dev/fdroid/." fdroid/ || (cd fdroid && fdroid init)
|
|
|
|
- name: Ensure F-Droid repo signing key and icon
|
|
run: |
|
|
cd fdroid
|
|
|
|
# Ensure repo icon exists (use app launcher icon)
|
|
mkdir -p repo/icons
|
|
if [ ! -f repo/icons/icon.png ]; then
|
|
cp ../android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png repo/icons/icon.png
|
|
fi
|
|
|
|
# If keystore doesn't exist, create the signing key.
|
|
# This only runs on the very first deployment; subsequent runs
|
|
# download the keystore from Hetzner via the scp step above.
|
|
if [ ! -f keystore.p12 ]; then
|
|
fdroid update --create-key
|
|
fi
|
|
|
|
- name: Copy new APK to repo
|
|
run: |
|
|
set -e
|
|
mkdir -p fdroid/repo
|
|
|
|
# Prefer tag name for release builds; fallback to ref name for manual runs.
|
|
REF_NAME="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
|
|
SAFE_REF_NAME="$(echo "$REF_NAME" | tr '/ ' '__' | tr -cd '[:alnum:]_.-')"
|
|
if [ -z "$SAFE_REF_NAME" ]; then
|
|
SAFE_REF_NAME="${GITHUB_SHA:-manual}"
|
|
fi
|
|
|
|
cp build/app/outputs/flutter-apk/app-release.apk "fdroid/repo/my_flutter_app_${SAFE_REF_NAME}.apk"
|
|
|
|
- name: Copy metadata to F-Droid repo
|
|
run: |
|
|
cp -r fdroid-metadata/* fdroid/metadata/
|
|
|
|
- name: Generate F-Droid Index
|
|
run: |
|
|
cd fdroid
|
|
fdroid update -c
|
|
|
|
- name: Upload Repo to Hetzner
|
|
env:
|
|
HOST: ${{ secrets.HETZNER_HOST }}
|
|
USER: ${{ secrets.HETZNER_USER }}
|
|
PASS: ${{ secrets.HETZNER_PASS }}
|
|
run: |
|
|
set -euo pipefail
|
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
|
|
|
|
# Create remote directory tree via SFTP batch (no exec channel needed).
|
|
# Leading '-' on each mkdir means "ignore error if already exists".
|
|
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
|
|
-mkdir dev
|
|
-mkdir dev/fdroid
|
|
-mkdir dev/fdroid/repo
|
|
SFTP
|
|
|
|
# Upload the entire fdroid/ directory (repo + keystore + config)
|
|
# so the signing key persists across runs.
|
|
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/. "$USER@$HOST:dev/fdroid/"
|