Skip to content

Merge pull request #598 from Nefrit0n/some_fixes #929

Merge pull request #598 from Nefrit0n/some_fixes

Merge pull request #598 from Nefrit0n/some_fixes #929

Workflow file for this run

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"