Skip to content

Merge branch 'main' of https://github.com/neverinfamous/R2-Manager-Wo… #279

Merge branch 'main' of https://github.com/neverinfamous/R2-Manager-Wo…

Merge branch 'main' of https://github.com/neverinfamous/R2-Manager-Wo… #279

name: Build and Push Docker Images
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
REGISTRY: docker.io
IMAGE_NAME: writenotenow/r2-bucket-manager
permissions:
contents: read
packages: write
security-events: write # For security scanning
pull-requests: write # For PR comments
id-token: write # For supply chain attestations
attestations: write # For generating attestations
jobs:
# Gate: Lint and build must pass before Docker images are created
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24.x"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Build frontend
run: npm run build
- name: Build worker
run: npx wrangler deploy --dry-run
# Gate: CodeQL security analysis must pass
codeql:
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
queries: security-extended,security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
upload: always
# Build each platform on native architecture (avoids QEMU emulation issues)
build-platform:
needs: [lint, codeql]
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
id-token: write
attestations: write
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
continue-on-error: true
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Read version from VERSION file
id: version
run: |
if [ -f "VERSION" ]; then
VERSION=$(head -1 VERSION | tr -d '[:space:]')
fi
if [ -z "$VERSION" ]; then
VERSION=$(grep -oP '"version":\s*"\K[0-9.]+' package.json | head -1)
fi
if [ -z "$VERSION" ]; then
VERSION="1.0.0"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION"
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
suffix=-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
tags: |
type=sha,prefix=sha-,format=short
- name: Build and push platform image
id: build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.platform }},mode=max
provenance: mode=max
sbom: true
- name: Export digest
if: github.event_name != 'pull_request'
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v6
with:
name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Security scan on amd64 image with strict gates (blocks on critical/high fixable vulns)
security-scan:
runs-on: ubuntu-latest
needs: build-platform
if: github.event_name != 'pull_request'
permissions:
contents: read
security-events: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build image for scanning
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: false
load: true
tags: local-scan:latest
cache-from: type=gha,scope=linux/amd64
- name: Docker Scout CVE Scan
uses: docker/scout-action@v1
with:
command: cves
image: local://local-scan:latest
exit-code: true
only-severities: critical,high
only-fixed: true
sarif-file: scout-results.sarif
- name: Upload Scout results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: scout-results.sarif
# Merge platform images into multi-arch manifest
merge-and-push:
runs-on: ubuntu-latest
needs: [build-platform, security-scan]
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
id-token: write
attestations: write
deployments: write
environment:
name: ${{ github.ref == 'refs/heads/main' && 'production' || '' }}
url: https://hub.docker.com/r/writenotenow/r2-bucket-manager
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Download digests
uses: actions/download-artifact@v7
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Read version
id: version
run: |
if [ -f "VERSION" ]; then
VERSION=$(head -1 VERSION | tr -d '[:space:]')
fi
if [ -z "$VERSION" ]; then
VERSION=$(grep -oP '"version":\s*"\K[0-9.]+' package.json | head -1)
fi
if [ -z "$VERSION" ]; then
VERSION="1.0.0"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Extract metadata for manifest
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=auto
tags: |
type=semver,pattern=v{{version}}
type=raw,value=v${{ steps.version.outputs.version }},enable={{is_default_branch}}
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix=sha-,format=short
- name: Create and push manifest
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect manifest
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
# Update Docker Hub description
- name: Update Docker Hub Description
if: github.ref == 'refs/heads/main'
uses: peter-evans/dockerhub-description@v5
continue-on-error: true
timeout-minutes: 5
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: ${{ env.IMAGE_NAME }}
readme-filepath: ./DOCKER_README.md
short-description: "Cloudflare R2 Manager - Batch Ops, Cross-Bucket Search, Share Links, S3 Import, Tags, GitHub SSO."
- name: Deployment Summary
if: github.ref == 'refs/heads/main'
run: |
echo "✅ Successfully published Docker images to production"
echo "🐳 Registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
echo "🏷️ Tags: ${{ steps.meta.outputs.tags }}"
echo "📝 Commit: ${{ github.sha }}"
echo "👤 Published by: ${{ github.actor }}"