diff --git a/.github/workflows/build.yaml b/.github/workflows/ci.yaml similarity index 71% rename from .github/workflows/build.yaml rename to .github/workflows/ci.yaml index e122e1f..d0e2303 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/ci.yaml @@ -1,16 +1,8 @@ -name: Build and Push +name: CI on: - push: - branches: [master] - paths: - - .github/workflows/build.yaml - - build/** pull_request: - paths: - - .github/workflows/build.yaml - - build/** - workflow_dispatch: + branches: [master] permissions: contents: read @@ -42,28 +34,24 @@ jobs: - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 - if: github.event_name != 'pull_request' with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build + - name: Build and push staging image uses: docker/build-push-action@v6 with: context: ./build - push: ${{ github.event_name != 'pull_request' }} - load: ${{ github.event_name == 'pull_request' }} - tags: | - ${{ env.IMAGE }}:latest - ${{ env.IMAGE }}:${{ needs.resolve-version.outputs.version }} + push: true + tags: ${{ env.IMAGE }}:pr-${{ github.event.pull_request.number }} build-args: | version=${{ needs.resolve-version.outputs.version }} - name: Trivy scan uses: aquasecurity/trivy-action@master with: - image-ref: ${{ env.IMAGE }}:${{ needs.resolve-version.outputs.version }} + image-ref: ${{ env.IMAGE }}:pr-${{ github.event.pull_request.number }} format: sarif output: trivy-results.sarif severity: CRITICAL,HIGH diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..054b1a4 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,145 @@ +name: Deploy + +on: + push: + branches: [master] + +permissions: + contents: read + packages: write + security-events: write + +env: + IMAGE: ghcr.io/${{ github.repository_owner }}/protonmail-bridge + +jobs: + detect-trigger: + runs-on: ubuntu-latest + outputs: + is_merge: ${{ steps.check.outputs.is_merge }} + pr_number: ${{ steps.check.outputs.pr_number }} + steps: + - name: Check if this push is from a merged PR + id: check + uses: actions/github-script@v7 + with: + script: | + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha + }); + const merged = prs.data.find(pr => pr.merged_at !== null); + if (merged) { + core.setOutput('is_merge', 'true'); + core.setOutput('pr_number', merged.number); + core.info(`Merged PR #${merged.number}`); + } else { + core.setOutput('is_merge', 'false'); + core.info('Direct push to master'); + } + + # Merge path: retag the staging image that CI already built and scanned + promote: + needs: detect-trigger + if: needs.detect-trigger.outputs.is_merge == 'true' + runs-on: ubuntu-latest + steps: + - name: Get latest upstream release + id: version + run: | + version=$(curl -s https://api.github.com/repos/ProtonMail/proton-bridge/releases/latest | jq -r '.tag_name') + echo "version=$version" >> $GITHUB_OUTPUT + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Retag staging image to production + run: | + docker buildx imagetools create \ + --tag ${{ env.IMAGE }}:latest \ + --tag ${{ env.IMAGE }}:${{ steps.version.outputs.version }} \ + ${{ env.IMAGE }}:pr-${{ needs.detect-trigger.outputs.pr_number }} + + - name: Delete staging tag + continue-on-error: true + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const prTag = 'pr-${{ needs.detect-trigger.outputs.pr_number }}'; + + const versions = await github.rest.packages.getAllPackageVersionsForPackageOwnedByUser({ + package_type: 'container', + package_name: 'protonmail-bridge', + username: owner, + per_page: 100 + }); + + const staging = versions.data.find(v => + v.metadata?.container?.tags?.includes(prTag) + ); + + if (staging) { + await github.rest.packages.deletePackageVersionForUser({ + package_type: 'container', + package_name: 'protonmail-bridge', + username: owner, + package_version_id: staging.id + }); + core.info(`Deleted staging tag ${prTag}`); + } else { + core.info(`Staging tag ${prTag} not found, skipping cleanup`); + } + + # Direct push path: full build pipeline (should be blocked by branch protection) + build: + needs: detect-trigger + if: needs.detect-trigger.outputs.is_merge == 'false' + runs-on: ubuntu-latest + steps: + - name: Get latest upstream release + id: version + run: | + version=$(curl -s https://api.github.com/repos/ProtonMail/proton-bridge/releases/latest | jq -r '.tag_name') + echo "version=$version" >> $GITHUB_OUTPUT + + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: ./build + push: true + tags: | + ${{ env.IMAGE }}:latest + ${{ env.IMAGE }}:${{ steps.version.outputs.version }} + build-args: | + version=${{ steps.version.outputs.version }} + + - name: Trivy scan + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.IMAGE }}:${{ steps.version.outputs.version }} + format: sarif + output: trivy-results.sarif + severity: CRITICAL,HIGH + + - name: Upload Trivy results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: trivy-results.sarif diff --git a/.github/workflows/scheduled-update.yaml b/.github/workflows/scheduled-update.yaml index 0a2bbdc..c10bddf 100644 --- a/.github/workflows/scheduled-update.yaml +++ b/.github/workflows/scheduled-update.yaml @@ -1,4 +1,4 @@ -name: Check for new upstream release +name: Scheduled Update on: schedule: