feat(scan): signaler les téléchargements en souffrance #111
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: Docker build & release | |
| # - Pull request vers main : build de validation (amd64, rien n'est poussé). | |
| # - Push sur dev : image de test `ghcr.io/<owner>/analysarr:dev` (+ `dev-<sha>`). | |
| # - Tag vX.Y.Z : image multi-arch versionnée, `latest` si plus haute version, | |
| # puis GitHub Release générée automatiquement. | |
| on: | |
| push: | |
| branches: [dev] | |
| tags: ["v*.*.*"] | |
| pull_request: | |
| branches: [main] | |
| permissions: {} | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| # Une release taguée n'est jamais annulée ; un push dev ou une PR remplace | |
| # simplement le build précédent devenu obsolète. | |
| cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| is_latest: ${{ steps.latest_check.outputs.is_latest }} | |
| image: ${{ steps.image.outputs.name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # nécessaire pour comparer ce tag aux autres ci-dessous | |
| - name: Resolve image name | |
| id: image | |
| # `IMAGE_NAME` (github.repository) est en casse mixte ("SEC844/Analysarr") ; | |
| # docker/metadata-action le met en minuscule en interne pour ses propres | |
| # tags, mais un registre OCI exige des minuscules — il faut le refaire | |
| # ici pour toutes les étapes qui construisent la référence elles-mêmes. | |
| run: echo "name=${REGISTRY}/${IMAGE_NAME,,}" >> "$GITHUB_OUTPUT" | |
| - name: Determine if this is the highest released version | |
| id: latest_check | |
| if: startsWith(github.ref, 'refs/tags/') | |
| # Le tag qui déclenche ce build n'est pas forcément le plus récent à | |
| # POUSSER sur GHCR : un rerun après un plantage ARM64 (déjà vu | |
| # plusieurs fois - voir historique des commits) peut faire pousser une | |
| # ancienne version APRÈS une plus récente, si son build a mis plus de | |
| # temps à repartir. Comparer par version (tri sémantique), jamais par | |
| # ordre de complétion du build, pour savoir si `latest` doit vraiment | |
| # pointer ici. | |
| run: | | |
| current="${GITHUB_REF_NAME#v}" | |
| highest=$(git tag -l 'v*.*.*' | sed 's/^v//' | sort -V | tail -n1) | |
| if [ "$current" = "$highest" ]; then | |
| echo "is_latest=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_latest=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up QEMU | |
| if: github.event_name != 'pull_request' | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| # GITHUB_TOKEN se voit refuser la création d'un tout nouveau package GHCR | |
| # sur un compte personnel (permission_denied: write_package), même avec | |
| # `permissions: packages: write` et le repo réglé sur "Read and write | |
| # permissions". CR_PAT est un token personnel (scope write:packages) qui | |
| # contourne cette limitation pour le premier push. | |
| password: ${{ secrets.CR_PAT }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| env: | |
| # Annotations posées aussi sur l'index multi-arch : c'est | |
| # `org.opencontainers.image.source` à ce niveau qui rattache le | |
| # package GHCR au dépôt (section "Packages" de la page du repo). | |
| DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }} | |
| type=sha,prefix=dev-,enable=${{ github.ref == 'refs/heads/dev' }} | |
| type=ref,event=pr | |
| labels: | | |
| org.opencontainers.image.title=Analysarr | |
| org.opencontainers.image.description=Tableau de bord self-hosted de santé des médias Emby/Jellyfin, Sonarr, Radarr et qBittorrent | |
| org.opencontainers.image.licenses=AGPL-3.0-only | |
| annotations: | | |
| org.opencontainers.image.title=Analysarr | |
| org.opencontainers.image.description=Tableau de bord self-hosted de santé des médias Emby/Jellyfin, Sonarr, Radarr et qBittorrent | |
| org.opencontainers.image.licenses=AGPL-3.0-only | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| # Validation de PR en amd64 seul : l'émulation ARM64 (QEMU) est lente | |
| # et sujette aux blocages, inutile tant que rien n'est publié. | |
| platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| annotations: ${{ steps.meta.outputs.annotations }} | |
| # Version affichée dans l'interface : "0.14.1" pour un tag, "dev" sur | |
| # la branche dev (la révision précise le commit). | |
| build-args: | | |
| APP_VERSION=${{ steps.meta.outputs.version }} | |
| APP_REVISION=${{ github.sha }} | |
| APP_BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} | |
| # Provenance + SBOM attachés aux images publiées : chacun peut | |
| # vérifier d'où vient l'image et ce qu'elle embarque. | |
| provenance: ${{ github.event_name != 'pull_request' && 'mode=max' || 'false' }} | |
| sbom: ${{ github.event_name != 'pull_request' }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Tag latest | |
| if: steps.latest_check.outputs.is_latest == 'true' | |
| # `imagetools create` aliase le manifest multi-arch déjà poussé | |
| # ci-dessus sous le tag `latest`, sans rebuild - seule cette étape | |
| # décide de `latest`, jamais docker/metadata-action (voir le check | |
| # de version ci-dessus pour pourquoi). | |
| env: | |
| IMAGE: ${{ steps.image.outputs.name }} | |
| run: docker buildx imagetools create --tag "${IMAGE}:latest" "${IMAGE}:${GITHUB_REF_NAME#v}" | |
| - name: Publish summary | |
| if: github.event_name != 'pull_request' | |
| env: | |
| IMAGE: ${{ steps.image.outputs.name }} | |
| TAGS: ${{ steps.meta.outputs.tags }} | |
| run: | | |
| package_url="https://github.com/${GITHUB_REPOSITORY}/pkgs/container/${IMAGE##*/}" | |
| { | |
| echo "## Image Docker publiée" | |
| echo | |
| echo "Disponible dans la section [Packages](${package_url}) du dépôt." | |
| echo | |
| echo '```' | |
| echo "$TAGS" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| create-release: | |
| needs: build-and-push | |
| if: startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # nécessaire pour lire le tag annoté et l'historique ci-dessous | |
| - name: Generate release notes | |
| # Notes = résumé du tag annoté + changelog regroupé par type à partir | |
| # des commits Conventional Commits depuis la version précédente + | |
| # instructions de récupération de l'image. Écrit dans un fichier | |
| # (body_path) : aucun contenu de commit n'est interpolé dans le shell. | |
| env: | |
| IMAGE: ${{ needs.build-and-push.outputs.image }} | |
| run: | | |
| tag="$GITHUB_REF_NAME" | |
| version="${tag#v}" | |
| # actions/checkout remplace le tag annoté par un tag léger pointant | |
| # sur le commit : %(contents) renverrait alors le message du commit | |
| # (ex : "Merge pull request #1") au lieu du résumé du tag. | |
| git fetch --force origin "refs/tags/${tag}:refs/tags/${tag}" | |
| previous=$(git tag -l 'v*.*.*' | sort -V | grep -B1 -x "$tag" | grep -vx "$tag" || true) | |
| range="${previous:+${previous}..}${tag}" | |
| subjects=$(git log --no-merges --format='%s' "$range") | |
| section() { | |
| local title="$1" pattern="$2" lines | |
| lines=$(printf '%s\n' "$subjects" | grep -E "$pattern" \ | |
| | sed -E 's/^[a-z]+\(([^)]+)\)!?: (.*)$/- **\1** : \2/; t; s/^[a-z]+!?: (.*)$/- \1/' || true) | |
| if [ -n "$lines" ]; then | |
| printf '### %s\n\n%s\n\n' "$title" "$lines" | |
| fi | |
| } | |
| { | |
| git tag -l --format='%(contents)' "$tag" | |
| echo | |
| echo "## Changements" | |
| echo | |
| section "Nouveautés" '^feat(\(.+\))?!?: ' | |
| section "Corrections" '^fix(\(.+\))?!?: ' | |
| section "Performances" '^perf(\(.+\))?!?: ' | |
| section "Maintenance" '^(refactor|build|ci|docs|chore|style|test)(\(.+\))?!?: ' | |
| if [ -n "$previous" ]; then | |
| echo "**Comparatif complet** : https://github.com/${GITHUB_REPOSITORY}/compare/${previous}...${tag}" | |
| echo | |
| fi | |
| echo "## Image Docker" | |
| echo | |
| echo '```bash' | |
| echo "docker pull ${IMAGE}:${version}" | |
| echo '```' | |
| echo | |
| echo "Plateformes : \`linux/amd64\`, \`linux/arm64\` — disponible dans la section [Packages](https://github.com/${GITHUB_REPOSITORY}/pkgs/container/${IMAGE##*/}) du dépôt." | |
| } > release-notes.md | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| body_path: release-notes.md | |
| # Par défaut cette action marque "Latest" la release la plus | |
| # RÉCEMMENT CRÉÉE, pas la plus récente en version - même défaut | |
| # d'ordre de complétion que pour le tag `latest` sur GHCR (voir | |
| # is_latest dans build-and-push, calculé une seule fois et transmis | |
| # ici plutôt que recalculé). | |
| make_latest: ${{ needs.build-and-push.outputs.is_latest }} |