From 41512c340ce609609160f956b5c89a3a238654c4 Mon Sep 17 00:00:00 2001 From: Marco D'Aleo Date: Sat, 10 Oct 2026 18:41:55 +0100 Subject: [PATCH] Split build workflow to integrate dev build --- .gitea/workflows/build-scan-push.yml | 169 --------------------------- .gitea/workflows/build.yml | 91 +++++++++++++++ .gitea/workflows/release.yml | 50 ++++++++ 3 files changed, 141 insertions(+), 169 deletions(-) delete mode 100644 .gitea/workflows/build-scan-push.yml create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitea/workflows/release.yml diff --git a/.gitea/workflows/build-scan-push.yml b/.gitea/workflows/build-scan-push.yml deleted file mode 100644 index b919798..0000000 --- a/.gitea/workflows/build-scan-push.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Build, Scan & Publish Image - -# Publishing a Gitea release (for example with tag 2026.09.25) builds the image, -# scans it with Grype (fails on fixable HIGH/CRITICAL findings), and pushes it to -# this repository's registry. A release pushes the release tag plus "latest" -# (non-prereleases only). A manual run builds and pushes only the tag supplied -# in the input. -on: - release: - types: [published] - workflow_dispatch: - inputs: - tag: - description: Image tag to build and push when run manually - required: false - default: latest - -concurrency: - group: image-${{ github.ref }} - cancel-in-progress: false - -jobs: - build-scan-push: - runs-on: running-man - env: - REGISTRY: git.sysmd.uk - IMAGE: git.sysmd.uk/mdaleo404/kovi - COSIGN_VERSION: v3.0.5 - GRYPE_VERSION: v0.110.0 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false - submodules: false - fetch-depth: 0 - - - name: Resolve release tag - env: - EVENT_TAG: ${{ github.event.release.tag_name }} - INPUT_TAG: ${{ github.event.inputs.tag }} - run: | - set -euo pipefail - TAG="${EVENT_TAG:-}" - if [ -z "$TAG" ]; then TAG="${INPUT_TAG:-}"; fi - if [ -z "$TAG" ]; then TAG="${GITHUB_REF_NAME:-}"; fi - if [ -z "$TAG" ]; then TAG="$(git describe --tags --exact-match 2>/dev/null || echo "")"; fi - if [ -z "$TAG" ]; then echo "Could not determine a tag for this run." >&2; exit 1; fi - SAFE_TAG="${TAG//\//-}" - case "$SAFE_TAG" in - [A-Za-z0-9_]*) ;; - *) SAFE_TAG="v$SAFE_TAG" ;; - esac - { - echo "TAG=$SAFE_TAG" - echo "IMAGE_REF=${IMAGE}:${SAFE_TAG}" - } >> "$GITHUB_ENV" - if [ -n "${EVENT_TAG:-}" ] && [ "${{ github.event.release.prerelease }}" != "true" ]; then - echo "PUSH_LATEST=1" >> "$GITHUB_ENV" - fi - echo "Resolved image reference: ${IMAGE}:${SAFE_TAG}" - - - name: Login to registry - run: | - echo "$REGISTRY_TOKEN" | docker login "${REGISTRY}" -u "$REGISTRY_USER" --password-stdin - env: - REGISTRY_USER: ${{ secrets.REGISTRY_USER }} - REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Build image - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - run: | - set -euo pipefail - TAGS=(-t "${IMAGE_REF}") - if [ "${PUSH_LATEST:-}" = "1" ]; then TAGS+=(-t "${IMAGE}:latest"); fi - docker buildx build \ - --load \ - --no-cache \ - --secret id=gitea_token,env=GITEA_TOKEN \ - "${TAGS[@]}" \ - . - - - name: Install Cosign - run: | - set -euo pipefail - curl -fLO "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" - chmod +x cosign-linux-amd64 - mv cosign-linux-amd64 /usr/local/bin/cosign - - - name: Install Grype - run: | - set -euo pipefail - - VERSION_NO_V="${GRYPE_VERSION#v}" - FILE="grype_${VERSION_NO_V}_linux_amd64.tar.gz" - BASE="https://github.com/anchore/grype/releases/download/${GRYPE_VERSION}" - - curl -fLO "${BASE}/${FILE}" - curl -fLO "${BASE}/grype_${VERSION_NO_V}_checksums.txt" - curl -fLO "${BASE}/grype_${VERSION_NO_V}_checksums.txt.sig" - curl -fLO "${BASE}/grype_${VERSION_NO_V}_checksums.txt.pem" - - cosign verify-blob \ - --signature "grype_${VERSION_NO_V}_checksums.txt.sig" \ - --certificate "grype_${VERSION_NO_V}_checksums.txt.pem" \ - --certificate-identity-regexp "https://github.com/anchore/grype" \ - --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ - "grype_${VERSION_NO_V}_checksums.txt" - - CHECK=$(grep " ${FILE}$" "grype_${VERSION_NO_V}_checksums.txt") - [ -n "$CHECK" ] || { echo "Missing checksum"; exit 1; } - - echo "$CHECK" | sha256sum -c - - - tar -xzf "${FILE}" - mv grype /usr/local/bin/ - - - name: Scan image with Grype - id: grype - run: | - grype "${IMAGE_REF}" -o json > grype.json - - echo "Fixable HIGH/CRITICAL vulnerabilities:" - jq -r ' - .matches[] - | select( - ( - .vulnerability.severity == "High" or - .vulnerability.severity == "Critical" - ) - and - ( - (.vulnerability.fix.versions | length) > 0 - ) - ) - | "\(.artifact.name)@\(.artifact.version) -> \(.vulnerability.id) [\(.vulnerability.severity)] | fixed: \(.vulnerability.fix.versions[0])" - ' grype.json - - # Fail the build on fixable HIGH/CRITICAL vulnerabilities. - COUNT=$(jq ' - [ - .matches[] - | select( - ( - .vulnerability.severity == "High" or - .vulnerability.severity == "Critical" - ) - and - ( - (.vulnerability.fix.versions | length) > 0 - ) - ) - ] - | length - ' grype.json) - echo "Fixable HIGH/CRITICAL count: ${COUNT}" - if [ "$COUNT" != "0" ]; then - echo "::error::Grype found ${COUNT} fixable HIGH/CRITICAL vulnerabilities." - exit 1 - fi - - - name: Push image - run: | - docker push "${IMAGE_REF}" - if [ "${PUSH_LATEST:-}" = "1" ]; then docker push "${IMAGE}:latest"; fi diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..3194154 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,91 @@ +name: Build, Scan & Publish Dev + +# Build, scan, and reconcile on push to main and nightly. Publishes a moving +# "dev" tag on real pushes only, once the reconciler gate passes. +on: + push: + branches: + - main + schedule: + - cron: "0 3 * * *" + workflow_dispatch: + +concurrency: + group: image-main + cancel-in-progress: false + +jobs: + build-scan-push: + runs-on: running-man + env: + REGISTRY: git.sysmd.uk + IMAGE: git.sysmd.uk/mdaleo404/kovi + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: false + fetch-depth: 0 + + - name: Resolve dev tag + run: | + set -euo pipefail + echo "IMAGE_REF=${IMAGE}:dev" >> "$GITHUB_ENV" + echo "Dev image: ${IMAGE}:dev" + + - name: Login to registry + run: | + echo "$REGISTRY_TOKEN" | docker login "${REGISTRY}" -u "$REGISTRY_USER" --password-stdin + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build image + run: | + set -euo pipefail + docker buildx build --load --no-cache -t "${IMAGE_REF}" . + + - name: Install Grype + uses: https://git.sysmd.uk/mdaleo404/actions/install-grype@v1 + + - name: Scan image with Grype + id: scan + continue-on-error: true + uses: https://git.sysmd.uk/mdaleo404/actions/scan-image@v1 + with: + image: ${{ env.IMAGE_REF }} + fail-on-fixable: 'false' + + - name: Reconcile Grype ignores + id: reconcile + continue-on-error: true + env: + GITEA_TOKEN: ${{ secrets.AUTOUPDATE_TOKEN }} + run: | + set -euo pipefail + curl -fsSL -H "Authorization: token ${GITEA_TOKEN}" \ + "https://git.sysmd.uk/api/v1/repos/Homelab/renovate-runner/raw/scripts/reconcile-grype.sh?ref=main" \ + -o /tmp/reconcile-grype.sh + bash /tmp/reconcile-grype.sh + + - name: Notify Node-RED on vulnerabilities + if: steps.reconcile.outcome == 'failure' + run: | + jq -n --arg repo "${GITHUB_REPOSITORY,,}" '{ repo: $repo }' \ + | curl -s -X POST https://nodered.sysmd.uk/vulns-alert \ + -H "Content-Type: application/json" \ + --data-binary @- + + - name: Fail on actionable vulnerabilities + if: steps.reconcile.outcome == 'failure' + run: exit 1 + + - name: Push dev image + if: github.event_name != 'schedule' && steps.reconcile.outcome != 'failure' + run: | + set -euo pipefail + docker push "${IMAGE_REF}" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e8b66f4 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,50 @@ +name: Publish Release Image + +# Promote the scanned dev image to the release tag (and latest); never rebuilds. +on: + release: + types: [published] + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + promote: + runs-on: running-man + env: + REGISTRY: git.sysmd.uk + IMAGE: git.sysmd.uk/mdaleo404/kovi + steps: + - name: Login to registry + run: | + echo "$REGISTRY_TOKEN" | docker login "${REGISTRY}" -u "$REGISTRY_USER" --password-stdin + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Resolve tags + env: + EVENT_TAG: ${{ github.event.release.tag_name }} + PRERELEASE: ${{ github.event.release.prerelease }} + run: | + set -euo pipefail + TAG="${EVENT_TAG:?release tag missing}" + { + echo "TAG=${TAG}" + echo "SOURCE_REF=${IMAGE}:dev" + } >> "$GITHUB_ENV" + if [ "${PRERELEASE:-}" != "true" ]; then + echo "PUSH_LATEST=1" >> "$GITHUB_ENV" + fi + echo "Promoting ${IMAGE}:dev -> ${IMAGE}:${TAG}" + + - name: Promote image + run: | + set -euo pipefail + TAGS=(-t "${IMAGE}:${TAG}") + if [ "${PUSH_LATEST:-}" = "1" ]; then TAGS+=(-t "${IMAGE}:latest"); fi + docker buildx imagetools create "${TAGS[@]}" "${SOURCE_REF}"