Merge pull request #598 from Nefrit0n/some_fixes #929
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Permissions выдаём на уровне каждого job — это безопаснее. | |
| # На workflow level оставляем минимум. | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: nefrit0n/red-lycoris | |
| COSIGN_VERSION: 'v2.4.0' | |
| jobs: | |
| # ─────────────────────────────────────────────────────────────── | |
| # 1. Автоматический bump тега по SemVer (0.1.1 -> 0.1.2 -> ...) | |
| # ─────────────────────────────────────────────────────────────── | |
| tag: | |
| name: 🏷️ Version | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # для пуша нового тега | |
| outputs: | |
| new_tag: ${{ steps.tag.outputs.new_tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine next version and push tag | |
| id: tag | |
| run: | | |
| set -euo pipefail | |
| # Нормализуем все теги — убираем "v", сортируем как SemVer, берём максимальный | |
| LATEST=$(git tag -l \ | |
| | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' \ | |
| | sed 's/^v//' \ | |
| | sort -V \ | |
| | tail -n1 || true) | |
| LATEST=${LATEST:-0.0.0} | |
| echo "Latest tag (normalized): $LATEST" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST" | |
| PATCH=$((PATCH + 1)) | |
| NEW_TAG="${MAJOR}.${MINOR}.${PATCH}" | |
| echo "New tag: $NEW_TAG" | |
| echo "new_tag=${NEW_TAG}" >> "$GITHUB_OUTPUT" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag "$NEW_TAG" | |
| git push origin "$NEW_TAG" | |
| build: | |
| name: 🐳 Build, Sign & SBOM — ${{ matrix.service }} | |
| needs: tag | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write # пушим образы в GHCR | |
| id-token: write # ВАЖНО: для cosign keyless через GitHub OIDC | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - service: backend | |
| context: ./backend | |
| - service: frontend | |
| context: ./frontend | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set build timestamp | |
| id: ts | |
| run: echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }} | |
| tags: | | |
| type=raw,value=latest | |
| type=raw,value=${{ needs.tag.outputs.new_tag }} | |
| type=sha,prefix=sha- | |
| - name: Build and push | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ${{ matrix.context }} | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| # Раздельный GHA-кэш для backend и frontend | |
| cache-from: type=gha,scope=${{ matrix.service }} | |
| cache-to: type=gha,scope=${{ matrix.service }},mode=max | |
| # provenance: false исключает hidden manifest, который ломает cosign | |
| # на некоторых конфигурациях. Если хочется build provenance — | |
| # лучше использовать SLSA generator отдельно. | |
| provenance: false | |
| # Build args из docker-compose.yml (только backend их использует, frontend проигнорирует) | |
| build-args: | | |
| VERSION=${{ needs.tag.outputs.new_tag }} | |
| COMMIT=${{ github.sha }} | |
| BUILD_DATE=${{ steps.ts.outputs.date }} | |
| # ──────────────────────────────────────────────── | |
| # Установка инструментов supply chain | |
| # ──────────────────────────────────────────────── | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@v3 | |
| with: | |
| cosign-release: ${{ env.COSIGN_VERSION }} | |
| - name: Install syft | |
| uses: anchore/sbom-action/download-syft@v0 | |
| # ──────────────────────────────────────────────── | |
| # Подпись образа по digest (а не по тегу!). | |
| # Тег — это указатель, может сместиться. | |
| # Digest sha256:... — это содержимое, оно неизменно. | |
| # ──────────────────────────────────────────────── | |
| - name: 🔐 Sign image with cosign (keyless) | |
| env: | |
| IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }}@${{ steps.build.outputs.digest }} | |
| run: | | |
| echo "Signing $IMAGE" | |
| cosign sign --yes "$IMAGE" | |
| # ──────────────────────────────────────────────── | |
| # SBOM: список всех зависимостей образа. | |
| # Генерируем в двух форматах для совместимости: | |
| # - CycloneDX (популярнее в коммерческих SCA) | |
| # - SPDX (стандарт ISO, любят аудиторы) | |
| # ──────────────────────────────────────────────── | |
| - name: 📋 Generate SBOM (CycloneDX + SPDX) | |
| env: | |
| IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }}@${{ steps.build.outputs.digest }} | |
| run: | | |
| syft "$IMAGE" -o cyclonedx-json=sbom-${{ matrix.service }}.cdx.json | |
| syft "$IMAGE" -o spdx-json=sbom-${{ matrix.service }}.spdx.json | |
| echo "SBOM size:" | |
| ls -lh sbom-${{ matrix.service }}.* | |
| - name: 📎 Attach SBOM to image as cosign attestation | |
| env: | |
| IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }}@${{ steps.build.outputs.digest }} | |
| run: | | |
| cosign attest --yes \ | |
| --predicate sbom-${{ matrix.service }}.cdx.json \ | |
| --type cyclonedx \ | |
| "$IMAGE" | |
| - name: ✍️ Sign SBOM file as standalone blob | |
| run: | | |
| cosign sign-blob --yes \ | |
| --output-signature sbom-${{ matrix.service }}.cdx.json.sig \ | |
| --output-certificate sbom-${{ matrix.service }}.cdx.json.pem \ | |
| sbom-${{ matrix.service }}.cdx.json | |
| - name: Upload SBOM artifacts for release job | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sbom-${{ matrix.service }} | |
| path: | | |
| sbom-${{ matrix.service }}.cdx.json | |
| sbom-${{ matrix.service }}.cdx.json.sig | |
| sbom-${{ matrix.service }}.cdx.json.pem | |
| sbom-${{ matrix.service }}.spdx.json | |
| retention-days: 90 | |
| # ─────────────────────────────────────────────────────────────── | |
| # 3. GitHub Release с автоматически сгенерированным changelog. | |
| # SBOM-файлы и их подписи прикрепляются как assets. | |
| # ─────────────────────────────────────────────────────────────── | |
| release: | |
| name: 📦 Release | |
| needs: [tag, build] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # создаём GitHub Release | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all SBOM artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: sbom-* | |
| path: sboms | |
| merge-multiple: true | |
| - name: List SBOM files for release | |
| run: ls -la sboms/ | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.tag.outputs.new_tag }} | |
| name: RedLycoris ${{ needs.tag.outputs.new_tag }} | |
| generate_release_notes: true | |
| files: | | |
| sboms/sbom-backend.cdx.json | |
| sboms/sbom-backend.cdx.json.sig | |
| sboms/sbom-backend.cdx.json.pem | |
| sboms/sbom-backend.spdx.json | |
| sboms/sbom-frontend.cdx.json | |
| sboms/sbom-frontend.cdx.json.sig | |
| sboms/sbom-frontend.cdx.json.pem | |
| sboms/sbom-frontend.spdx.json | |
| # ─────────────────────────────────────────────────────────────── | |
| # 4. Deployment в production environment. | |
| # ─────────────────────────────────────────────────────────────── | |
| deploy: | |
| name: 🌺 Deploy RedLycoris | |
| needs: [tag, build, release] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| deployments: write | |
| environment: | |
| name: production | |
| url: https://github.com/${{ github.repository }}/releases/tag/${{ needs.tag.outputs.new_tag }} | |
| steps: | |
| - name: Deployment summary | |
| run: | | |
| { | |
| echo "## 🌺 RedLycoris ${{ needs.tag.outputs.new_tag }} deployed" | |
| echo "" | |
| echo "| | |" | |
| echo "|---|---|" | |
| echo "| **Version** | \`${{ needs.tag.outputs.new_tag }}\` |" | |
| echo "| **Backend image** | \`ghcr.io/${{ github.repository }}/backend:${{ needs.tag.outputs.new_tag }}\` |" | |
| echo "| **Frontend image** | \`ghcr.io/${{ github.repository }}/frontend:${{ needs.tag.outputs.new_tag }}\` |" | |
| echo "| **Release** | [View on GitHub](https://github.com/${{ github.repository }}/releases/tag/${{ needs.tag.outputs.new_tag }}) |" | |
| echo "| **Commit** | \`${{ github.sha }}\` |" | |
| echo "| **Signed** | ✅ via Sigstore cosign (keyless OIDC) |" | |
| echo "| **SBOM** | ✅ CycloneDX + SPDX attached to release |" | |
| } >> "$GITHUB_STEP_SUMMARY" |