Repository navigation
Beta release #34
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: Beta release | |
| # Publie une PRE-RELEASE GitHub consommee par le canal beta de l'application. | |
| # | |
| # Difference avec "Test build" : ce workflow PUBLIE, donc les APK arrivent en OTA chez les | |
| # testeurs qui ont coche « Canal de mise a jour beta ». Ne le declencher que sur un etat du | |
| # code qu'on accepte de diffuser. | |
| # | |
| # Le numero de version installe DOIT correspondre au tag, sinon la mise a jour est reproposee | |
| # en boucle : d'ou -Pmg4.versionSuffix, qui injecte le suffixe dans le versionName de l'APK. | |
| # | |
| # La release stable (release.yml) reste prioritaire : a numero egal, une pre-release est | |
| # consideree ANTERIEURE (precedence semver), donc la stable remplace toujours la derniere beta. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| label: | |
| description: "Identifiant de la beta (ex: beta1, rc2)" | |
| type: string | |
| default: beta | |
| notes: | |
| description: "Ce qu'il faut tester / ce qui a change" | |
| type: string | |
| default: "" | |
| keep: | |
| description: "Nombre de betas a conserver (1 = seulement celle-ci ; 0 = ne rien supprimer)" | |
| type: string | |
| default: "3" | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| env: | |
| MG4_KEYSTORE_BASE64: ${{ secrets.MG4_KEYSTORE_BASE64 }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| - name: Decode platform keystore | |
| run: | | |
| if [ -z "$MG4_KEYSTORE_BASE64" ]; then | |
| echo "::error::MG4_KEYSTORE_BASE64 absent. Une beta NON signee par la cle plateforme" | |
| echo "::error::serait refusee a l'installation par le controle de signature (T-901)." | |
| exit 1 | |
| fi | |
| printf '%s' "$MG4_KEYSTORE_BASE64" | tr -d '[:space:]' | base64 -d > "$RUNNER_TEMP/platform.keystore" | |
| if [ "$(head -c1 "$RUNNER_TEMP/platform.keystore" | od -An -tx1 | tr -d ' ')" != "30" ]; then | |
| echo "::error::MG4_KEYSTORE_BASE64 est invalide. Recreer le secret avec : base64 -w0 platform.keystore" | |
| exit 1 | |
| fi | |
| echo "MG4_KEYSTORE=$RUNNER_TEMP/platform.keystore" >> "$GITHUB_ENV" | |
| # Suffixe unique et croissant : le numero de run garantit qu'une beta publiee apres une | |
| # autre est bien vue comme posterieure, meme si le libelle saisi est identique. | |
| - name: Compute version | |
| id: ver | |
| run: | | |
| # sed plutot que grep -oP : PCRE depend de la locale du runner, sed non. | |
| BASE=$(sed -n 's/.*versionName = "\([^"]*\)".*/\1/p' app/build.gradle.kts | head -1) | |
| if [ -z "$BASE" ]; then | |
| echo "::error::versionName introuvable dans app/build.gradle.kts"; exit 1 | |
| fi | |
| SUFFIX="-${{ inputs.label }}.${{ github.run_number }}" | |
| echo "base=$BASE" >> "$GITHUB_OUTPUT" | |
| echo "suffix=$SUFFIX" >> "$GITHUB_OUTPUT" | |
| echo "full=${BASE}${SUFFIX}" >> "$GITHUB_OUTPUT" | |
| echo "Version beta : ${BASE}${SUFFIX}" | |
| - name: Build online + offline APKs | |
| env: | |
| MG4_KEYSTORE_PASSWORD: ${{ secrets.MG4_KEYSTORE_PASSWORD }} | |
| MG4_KEY_ALIAS: ${{ secrets.MG4_KEY_ALIAS }} | |
| MG4_KEY_PASSWORD: ${{ secrets.MG4_KEY_PASSWORD }} | |
| run: | | |
| chmod +x gradlew | |
| ./gradlew assembleOnlineRelease assembleOfflineRelease --no-daemon \ | |
| -Pmg4.versionSuffix='${{ steps.ver.outputs.suffix }}' | |
| - name: Collect APKs | |
| run: | | |
| mkdir -p dist | |
| find app/build/outputs/apk -name "MG4Control-*.apk" -exec cp {} dist/ \; | |
| ls -la dist/ | |
| # Controle que l'APK annonce bien la version du tag : c'est la condition pour que l'OTA | |
| # cesse de proposer la mise a jour une fois installee. | |
| - name: Verify APK version matches the tag | |
| run: | | |
| AAPT=$(ls "$ANDROID_SDK_ROOT"/build-tools/*/aapt2 | sort -V | tail -1) | |
| EXPECTED='${{ steps.ver.outputs.full }}' | |
| for f in dist/*.apk; do | |
| GOT=$("$AAPT" dump badging "$f" | grep -oP "versionName='\K[^']+") | |
| echo "$(basename "$f") -> $GOT" | |
| case "$GOT" in | |
| "$EXPECTED"|"$EXPECTED-offline") ;; | |
| *) echo "::error::$(basename "$f") annonce '$GOT', attendu '$EXPECTED'"; exit 1 ;; | |
| esac | |
| done | |
| # /!\ --min-sdk-version 23 est OBLIGATOIRE pour que ce rapport dise la verite. Sans lui, | |
| # apksigner prend le minSdk de l'APK (28) et ne verifie qu'en v3 : il affiche alors | |
| # "Verified using v1 scheme: false" sur un APK qui CONTIENT bien une signature v1. | |
| # Ce faux negatif a deja fait chercher une panne de cle la ou le reseau etait en cause. | |
| - name: Verify signature schemes | |
| run: | | |
| APKSIGNER=$(ls "$ANDROID_SDK_ROOT"/build-tools/*/apksigner | sort -V | tail -1) | |
| for f in dist/*.apk; do | |
| echo "-- $(basename "$f") --" | |
| "$APKSIGNER" verify --print-certs -v --min-sdk-version 23 "$f" \ | |
| | grep -E 'Verified using v[0-9]|certificate SHA-256 digest' | |
| # Presence REELLE de la signature v1, independamment de ce qu'apksigner choisit de | |
| # verifier. Elle est indispensable : sur AAOS 9 le controle OTA lit l'archive via | |
| # getPackageArchiveInfo(), qui ne remonte aucun certificat sans v1 - l'application | |
| # refuse alors la mise a jour. Mieux vaut casser le build que publier ca. | |
| if ! unzip -l "$f" | grep -qE 'META-INF/.*[.](SF|RSA|DSA|EC)$'; then | |
| echo "::error::$(basename "$f") n a PAS de signature v1 (JAR) - l OTA la refuserait" | |
| exit 1 | |
| fi | |
| # Empreinte du fichier publie. Le vehicule journalise la meme chose apres | |
| # telechargement ("APK telecharge : N octets, sha256=..."). Deux lignes identiques | |
| # prouvent un transfert intact ; une divergence designe le reseau, pas la signature. | |
| echo " v1 (JAR) presente : OUI" | |
| echo " taille : $(stat -c%s "$f") octets" | |
| echo " sha256 : $(sha256sum "$f" | cut -d' ' -f1)" | |
| done | |
| - name: Publish pre-release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.ver.outputs.full }} | |
| # Sans target_commitish, GitHub cree le tag sur la branche par defaut : la beta 2.6.6-beta.2 | |
| # a ainsi ete taguee sur main alors qu'elle etait construite depuis beta-OTA. L'APK etait | |
| # bonne, mais le tag pointait vers un code qui n'etait pas celui livre. | |
| target_commitish: ${{ github.sha }} | |
| name: MG4Control ${{ steps.ver.outputs.full }} | |
| prerelease: true | |
| files: dist/*.apk | |
| body: | | |
| ## MG4Control ${{ steps.ver.outputs.full }} — test build | |
| > Delivered only to users who enabled | |
| > **Settings -> Advanced settings -> Beta update channel**. | |
| > Rolling back to an earlier version is not possible through the updater. | |
| ${{ inputs.notes }} | |
| | Variant | File | Network | Auto-update | | |
| |---|---|---|---| | |
| | **Online** | `MG4Control-online-*.apk` | yes | yes | | |
| | **Offline** | `MG4Control-secure-*-offline.apk` | none | no | | |
| # Purge des anciennes betas. Trois garde-fous, dans cet ordre : | |
| # 1. on ne touche QU'AUX prereleases (select .prerelease==true) — une erreur de filtre | |
| # effacerait les releases stables, d'ou dependent tous les utilisateurs ; | |
| # 2. on ne supprime jamais celle qu'on vient de publier ; | |
| # 3. si elle n'apparait pas encore dans l'API, on n'efface RIEN — mieux vaut une liste | |
| # trop longue qu'une suppression decidee sur un etat incomplet. | |
| # | |
| # On garde les N plus recentes parce que l'OTA ne sait PAS redescendre de version : le seul | |
| # retour arriere depuis une beta cassee est de reinstaller l'APK precedent a la main. Purger | |
| # jusqu'a une seule supprimerait ce filet. | |
| # | |
| # Regle supplementaire : une beta dont la STABLE de meme numero est sortie ne sert plus a | |
| # rien (la stable la remplace pour tout le monde), elle part quel que soit son rang. | |
| - name: Prune old beta releases | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| KEEP_TAG: v${{ steps.ver.outputs.full }} | |
| KEEP_N: ${{ inputs.keep }} | |
| run: | | |
| set -euo pipefail | |
| if ! [ "$KEEP_N" -gt 0 ] 2>/dev/null; then | |
| echo "keep=$KEEP_N : purge desactivee."; exit 0 | |
| fi | |
| gh api "repos/$GITHUB_REPOSITORY/releases?per_page=100" > all.json | |
| # Numeros des releases stables publiees (sans le "v" ni le suffixe). | |
| jq -r '.[] | select(.prerelease==false and .draft==false) | .tag_name' all.json \ | |
| | sed 's/^v//' | cut -d- -f1 | sort -u > stables.txt | |
| # Prereleases, de la plus recente a la plus ancienne (ordre de l'API). | |
| jq -r '.[] | select(.prerelease==true) | .tag_name' all.json > betas.txt | |
| echo "Stables publiees : $(tr '\n' ' ' < stables.txt)" | |
| echo "Prereleases : $(wc -l < betas.txt)" | |
| if ! grep -qx "$KEEP_TAG" betas.txt; then | |
| echo "::warning::$KEEP_TAG absente de l'API (propagation ?) — purge annulee par securite" | |
| exit 0 | |
| fi | |
| kept=0 | |
| while read -r tag; do | |
| if [ -z "$tag" ]; then continue; fi | |
| base=$(echo "${tag#v}" | cut -d- -f1) | |
| if [ "$tag" != "$KEEP_TAG" ] && grep -qx "$base" stables.txt; then | |
| echo " supprime $tag — la stable $base est publiee" | |
| gh release delete "$tag" --yes --cleanup-tag | |
| continue | |
| fi | |
| kept=$((kept + 1)) | |
| if [ "$kept" -le "$KEEP_N" ] || [ "$tag" = "$KEEP_TAG" ]; then | |
| echo " conserve $tag ($kept/$KEEP_N)" | |
| continue | |
| fi | |
| echo " supprime $tag — au-dela des $KEEP_N conservees" | |
| gh release delete "$tag" --yes --cleanup-tag | |
| done < betas.txt |