name: Release # No image registry involved: Portainer is wired to this repo's Git remote # and builds/redeploys apps/api/Dockerfile itself. This workflow sanity-checks # that the image actually builds at the tagged commit, builds+attaches a # signed Android APK, publishes a Forgejo Release, then (best-effort) pings # Portainer's deploy webhook so it doesn't have to wait for its own polling # interval. on: push: tags: ["v*.*.*"] permissions: contents: write jobs: sanity-build: runs-on: ubuntu-latest steps: # This runner's job containers don't come with docker preinstalled — # same reason ci.yml's `test` job has to do this before its own # `docker run` calls. - name: Install docker run: apt-get update && apt-get install -y docker.io - uses: https://github.com/actions/checkout@v4 # Build only, no push, no registry — just confirms Portainer will be # able to build this same Dockerfile successfully at this tag before # anyone points it at this ref. - run: docker build -f apps/api/Dockerfile . # Builds the same signed APK as ci.yml's build-android job (issue #64), # with one difference: the version baked into the APK is derived from the # tag itself rather than left at build.gradle's default. Duplicated here # rather than factored into a shared `workflow_call` reusable workflow — # deliberate: reusable-workflow support on this self-hosted Forgejo/act # runner is unverified, and this ~15-line recipe is proven working as-is # in ci.yml; not worth risking a new class of runner-compatibility surprise # (this repo has already hit several — see ci-forgejo-runner-constraints # in project memory) to save a little duplication. Keep both copies in # sync if the Android build steps ever change. build-android-apk: runs-on: ubuntu-latest steps: - uses: https://github.com/actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: https://github.com/actions/setup-node@v4 with: node-version: 22 cache: pnpm - uses: https://github.com/actions/setup-java@v4 with: distribution: temurin java-version: "21" - uses: https://github.com/android-actions/setup-android@v3 - run: pnpm install --frozen-lockfile - name: Build le bundle web pour Capacitor env: VITE_API_URL: ${{ secrets.MOBILE_API_URL }} run: pnpm --filter web build:capacitor - run: pnpm --filter web exec cap sync android - name: Décode le keystore de release env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/web/android/app/release.keystore - name: Dérive versionName/versionCode depuis le tag # GITHUB_REF_NAME = "v1.4.2" pour un tag `v1.4.2` — versionName = # "1.4.2" (sans le `v`), versionCode = majeur*10000 + mineur*100 + # correctif (ex. 1.4.2 -> 10402), un entier strictement croissant à # chaque release comme Android l'exige (jamais réutilisé, jamais # décroissant) tant que chaque partie reste sous 100 (largement # suffisant ici). Exposé aux steps suivants via $GITHUB_ENV plutôt # que recalculé. run: | tag="${GITHUB_REF_NAME#v}" major="$(echo "$tag" | cut -d. -f1)" minor="$(echo "$tag" | cut -d. -f2)" patch="$(echo "$tag" | cut -d. -f3)" # Préfixe 10# : force une lecture en base 10, sinon bash lirait # un composant à zéro non significatif (ex. "08") comme de # l'octal invalide et échouerait. code=$((10#$major * 10000 + 10#$minor * 100 + 10#$patch)) echo "Version dérivée du tag $GITHUB_REF_NAME : versionName=$tag versionCode=$code" echo "ANDROID_VERSION_NAME=$tag" >> "$GITHUB_ENV" echo "ANDROID_VERSION_CODE=$code" >> "$GITHUB_ENV" - name: Build l'APK signé working-directory: apps/web/android env: ANDROID_KEYSTORE_PATH: release.keystore ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | chmod +x ./gradlew ./gradlew assembleRelease -PandroidVersionName="$ANDROID_VERSION_NAME" -PandroidVersionCode="$ANDROID_VERSION_CODE" # Nom de fichier stable, jamais versionné : la page d'accueil (#65) # pointe vers /releases/latest (Forgejo n'a pas l'équivalent du # raccourci GitHub /releases/latest/download/, seul # /releases/latest lui-même résout) — un nom stable évite au moins que # l'utilisateur voie un nom de fichier différent à chaque release une # fois sur la page. - name: Renomme l'APK sous un nom stable run: cp apps/web/android/app/build/outputs/apk/release/app-release.apk batchcooking.apk # v4+ exige l'API "Results" de GitHub, non supportée par ce Forgejo # self-hosted ("GHESNotSupportedError") — v3 (ancienne API Artifacts) # est la seule utilisable ici, voir ci.yml's build-android pour la # même contrainte déjà rencontrée. - uses: https://github.com/actions/upload-artifact@v3 with: name: batchcooking.apk path: batchcooking.apk forgejo-release: runs-on: ubuntu-latest needs: [sanity-build, build-android-apk] steps: # Ce runner ne fournit ni docker ni curl par défaut dans ses # conteneurs de job — même constat déjà fait pour docker (voir # sanity-build) et pour curl dans ci.yml's test job. - name: Install curl run: apt-get update && apt-get install -y curl - uses: https://github.com/actions/checkout@v4 - uses: https://github.com/actions/download-artifact@v3 with: name: batchcooking.apk # softprops/action-gh-release (l'action GitHub habituelle pour ça) # échoue systématiquement contre ce Forgejo : sa requête de création # de release répond 405 (vérifié en conditions réelles, tag v0.2.0 — # pas un souci de ref bare comme les autres actions de ce fichier, # une vraie incompatibilité d'API entre les deux plateformes qui n'a # pas de contournement connu côté configuration de l'action). Appelle # directement l'API native de Forgejo plutôt qu'une action tierce # dont la compatibilité GitHub-Forgejo n'est pas garantie. # # Pas d'équivalent à `generate_release_notes` ici (fonctionnalité # propre à GitHub, pas répliquée par l'API Forgejo) — le corps de la # release reste minimal, cohérent avec l'absence d'outillage de # changelog ailleurs dans le repo. - name: Crée (ou récupère) la Release Forgejo id: create-release env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" body="$(printf '{"tag_name":"%s","name":"%s","draft":false,"prerelease":false}' "$GITHUB_REF_NAME" "$GITHUB_REF_NAME")" # POST pour créer la release, code HTTP capturé à part. Si le tag est # déjà connu de Forgejo (tag poussé à la main avant ce workflow, ou # ré-exécution du job), l'API répond 409 : on réutilise alors la # release existante au lieu d'échouer — idempotence, cf. l'incident # du tag v0.2.2 poussé en avance qui a fait tomber ce job sur un 409. code="$(curl -sS -o /tmp/release.json -w '%{http_code}' -X POST "$api" \ -H "Authorization: token $FORGEJO_TOKEN" \ -H "Content-Type: application/json" \ -d "$body")" if [ "$code" = "409" ]; then echo "Release $GITHUB_REF_NAME déjà existante (HTTP 409) — récupération de son id." curl -fsS "$api/tags/$GITHUB_REF_NAME" \ -H "Authorization: token $FORGEJO_TOKEN" -o /tmp/release.json elif [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then echo "Échec de création de la Release (HTTP $code) : $(cat /tmp/release.json)" >&2 exit 1 fi id="$(grep -o '"id":[0-9]*' /tmp/release.json | head -1 | cut -d: -f2)" if [ -z "$id" ]; then echo "Pas d'id de release trouvé : $(cat /tmp/release.json)" >&2 exit 1 fi echo "Release $GITHUB_REF_NAME prête (id=$id)" echo "release-id=$id" >> "$GITHUB_OUTPUT" - name: Attache l'APK à la Release env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/${{ steps.create-release.outputs.release-id }}/assets" curl -fsS -X POST "$api?name=batchcooking.apk" \ -H "Authorization: token $FORGEJO_TOKEN" \ -F "attachment=@batchcooking.apk" notify-portainer: runs-on: ubuntu-latest needs: forgejo-release steps: - name: Trigger Portainer redeploy # Best-effort: does nothing (and doesn't fail the workflow) until # the PORTAINER_WEBHOOK_URL repo secret is set — grab that URL from # the stack's webhook setting in Portainer and add it as a secret # named PORTAINER_WEBHOOK_URL (see README). env: PORTAINER_WEBHOOK_URL: ${{ secrets.PORTAINER_WEBHOOK_URL }} run: | if [ -z "$PORTAINER_WEBHOOK_URL" ]; then echo "PORTAINER_WEBHOOK_URL not set — skipping, Portainer will pick this up on its next poll." exit 0 fi curl -fsS -X POST "$PORTAINER_WEBHOOK_URL"