feat: remote console (#89) — phases 1a→3 + per-box toggle + WS TLS pinning #239
Workflow file for this run
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: Docker Build and Push | |
| on: | |
| push: | |
| branches: [main] | |
| tags: | |
| - 'v*' | |
| pull_request: | |
| branches: [main] | |
| # Cancel superseded PR runs; let main/tag builds finish (they push images). | |
| concurrency: | |
| group: docker-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: {} | |
| env: | |
| REGISTRY: ghcr.io | |
| jobs: | |
| changes: | |
| name: Detect changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| apps: ${{ steps.apps.outputs.apps }} | |
| steps: | |
| - name: Harden runner | |
| uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Filter changed paths | |
| id: filter | |
| # Tag pushes have no base ref to diff against, so always-build is the | |
| # safe default (a tag means we want to release both images). | |
| if: github.event_name != 'push' || github.ref_type != 'tag' | |
| uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| with: | |
| filters: | | |
| gearbox: | |
| - 'gearbox/**' | |
| - '.github/workflows/docker.yml' | |
| gearbox-agent: | |
| - 'gearbox-agent/**' | |
| - '.github/workflows/docker.yml' | |
| - name: Build matrix from filter results | |
| id: apps | |
| env: | |
| IS_TAG: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} | |
| GEARBOX: ${{ steps.filter.outputs.gearbox }} | |
| AGENT: ${{ steps.filter.outputs.gearbox-agent }} | |
| run: | | |
| if [ "$IS_TAG" = "true" ]; then | |
| echo 'apps=["gearbox","gearbox-agent"]' >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| apps=() | |
| [ "$GEARBOX" = "true" ] && apps+=("gearbox") | |
| [ "$AGENT" = "true" ] && apps+=("gearbox-agent") | |
| if [ ${#apps[@]} -eq 0 ]; then | |
| echo "apps=[]" >> "$GITHUB_OUTPUT" | |
| else | |
| printf -v joined '"%s",' "${apps[@]}" | |
| echo "apps=[${joined%,}]" >> "$GITHUB_OUTPUT" | |
| fi | |
| build-and-push: | |
| name: Build and Push (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.apps != '[]' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| attestations: write | |
| id-token: write | |
| security-events: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: ${{ fromJSON(needs.changes.outputs.apps) }} | |
| env: | |
| IMAGE_NAME: ${{ github.repository }}/${{ matrix.app }} | |
| steps: | |
| - name: Harden runner | |
| uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=sha,prefix=sha- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| # Build amd64-only locally so we can scan it before deciding to push. | |
| # Cache is shared with the multi-platform push step below, so the | |
| # incremental cost of the second build is essentially zero. | |
| - name: Build image for scan | |
| id: scan-build | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v6 | |
| with: | |
| context: ./${{ matrix.app }} | |
| push: false | |
| load: true | |
| tags: ${{ matrix.app }}:scan | |
| cache-from: type=gha,scope=${{ matrix.app }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.app }} | |
| build-args: | | |
| VERSION=${{ github.ref_name }} | |
| COMMIT_SHA=${{ github.sha }} | |
| BUILD_DATE=${{ github.event.repository.updated_at }} | |
| # Gating scan: fail the build on CRITICAL/HIGH unfixed-or-fixed in | |
| # production releases, but only warn on PRs (so authors can iterate | |
| # without being blocked by upstream-only CVEs). | |
| - name: Run Trivy image scan (gating) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| image-ref: ${{ matrix.app }}:scan | |
| format: 'sarif' | |
| output: 'trivy-image-results.sarif' | |
| severity: 'CRITICAL,HIGH' | |
| ignore-unfixed: true | |
| exit-code: ${{ github.event_name == 'pull_request' && '0' || '1' }} | |
| - name: Upload Trivy results to GitHub Security | |
| uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-image-results.sarif' | |
| category: trivy-docker-${{ matrix.app }} | |
| - name: Log in to Container Registry | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Multi-platform build + push, only after the scan has passed. Reuses | |
| # the cache from the scan build so amd64 layers are essentially free. | |
| - name: Build and push (multi-platform) | |
| id: push | |
| if: github.event_name != 'pull_request' | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v6 | |
| with: | |
| context: ./${{ matrix.app }} | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=${{ matrix.app }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.app }} | |
| build-args: | | |
| VERSION=${{ github.ref_name }} | |
| COMMIT_SHA=${{ github.sha }} | |
| BUILD_DATE=${{ github.event.repository.updated_at }} | |
| platforms: linux/amd64,linux/arm64 | |
| - name: Generate artifact attestation | |
| if: github.event_name != 'pull_request' && !github.event.repository.private | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| subject-digest: ${{ steps.push.outputs.digest }} | |
| push-to-registry: true | |
| docker-status: | |
| name: Docker Status | |
| needs: [changes, build-and-push] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Verify required jobs succeeded or skipped intentionally | |
| env: | |
| CHANGES: ${{ needs.changes.result }} | |
| BUILD: ${{ needs.build-and-push.result }} | |
| run: | | |
| fail=0 | |
| for job in CHANGES BUILD; do | |
| result="${!job}" | |
| case "$result" in | |
| success|skipped) ;; | |
| *) echo "::error::Job $job ended with result: $result"; fail=1 ;; | |
| esac | |
| done | |
| exit $fail |