name: "Release probod" on: push: tags: - "probod/v*" permissions: contents: "read" jobs: build-apps: name: "build-apps" runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache" permissions: contents: "read" steps: - uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6 with: submodules: recursive - uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2 - uses: "./.github/actions/setup" with: go: "false" - run: "npm --workspace @probo/emails run build" - run: "make relay" - name: "Build console" run: | npm --workspace @probo/console run check NODE_ENV=production npm --workspace @probo/console run build - name: "Build compliance-portal" run: | npm --workspace @probo/compliance-portal run check NODE_ENV=production npm --workspace @probo/compliance-portal run build - uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7 with: name: "frontend-apps" path: | apps/console/dist/ apps/compliance-portal/dist/ packages/emails/dist/ retention-days: 1 build-binary: name: "binary (${{ matrix.goos }}/${{ matrix.goarch }})" needs: [build-apps] runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache" permissions: contents: "read" strategy: fail-fast: false matrix: include: - { goos: linux, goarch: amd64 } - { goos: linux, goarch: arm64 } - { goos: darwin, goarch: amd64 } - { goos: darwin, goarch: arm64 } - { goos: windows, goarch: amd64 } - { goos: freebsd, goarch: amd64 } - { goos: freebsd, goarch: arm64 } - { goos: openbsd, goarch: amd64 } - { goos: openbsd, goarch: arm64 } steps: - uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6 with: submodules: recursive - uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2 - uses: "./.github/actions/setup" with: node: "false" - uses: "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8 with: name: "frontend-apps" - name: "Generate Go code" run: | go generate ./pkg/server/api/connect/v1 go generate ./pkg/server/api/mcp/v1 go generate ./pkg/server/api/console/v1 go generate ./pkg/server/api/complianceportal/v1 - name: "Build binaries" env: CGO_ENABLED: "0" GOOS: "${{ matrix.goos }}" GOARCH: "${{ matrix.goarch }}" run: | VERSION="${GITHUB_REF_NAME##*/v}" BOOTSTRAP_VERSION="$(cat cmd/probod-bootstrap/VERSION)" EXT="" if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi go build -ldflags "-s -w -X 'main.version=${VERSION}' -X 'main.env=prod'" \ -gcflags="-e" -o "dist/probod${EXT}" ./cmd/probod/main.go go build -ldflags "-s -w -X 'main.version=${BOOTSTRAP_VERSION}'" \ -gcflags="-e" -o "dist/probod-bootstrap${EXT}" ./cmd/probod-bootstrap/main.go - name: "Create archive" env: GOOS: "${{ matrix.goos }}" GOARCH: "${{ matrix.goarch }}" run: | case "$GOOS" in linux) OS="Linux" ;; darwin) OS="Darwin" ;; windows) OS="Windows" ;; freebsd) OS="Freebsd" ;; openbsd) OS="Openbsd" ;; esac case "$GOARCH" in amd64) ARCH="x86_64" ;; *) ARCH="$GOARCH" ;; esac EXT="" if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi mkdir -p archives PROBOD_DIR="probod_${OS}_${ARCH}" mkdir -p "staging/${PROBOD_DIR}" cp "dist/probod${EXT}" "dist/probod-bootstrap${EXT}" \ README.md LICENSE cmd/probod/CHANGELOG.md "staging/${PROBOD_DIR}/" if [ "$GOOS" = "windows" ]; then (cd staging && zip -r "../archives/${PROBOD_DIR}.zip" "${PROBOD_DIR}") else tar -czf "archives/${PROBOD_DIR}.tar.gz" -C staging "${PROBOD_DIR}" fi - uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7 with: name: "binary-${{ matrix.goos }}-${{ matrix.goarch }}" path: "dist/" retention-days: 1 - uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7 with: name: "archive-${{ matrix.goos }}-${{ matrix.goarch }}" path: "archives/" retention-days: 1 build-docker: name: "docker (${{ matrix.arch }})" needs: [build-binary] runs-on: "runs-on=${{ github.run_id }}/runner=${{ matrix.runner }}/extras=s3-cache" permissions: contents: "read" packages: "write" strategy: fail-fast: false matrix: include: - arch: amd64 platform: "linux/amd64" runner: "4cpu-linux-x64" - arch: arm64 platform: "linux/arm64" runner: "4cpu-linux-arm64" steps: - uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6 - uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2 - uses: "docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" # v4.1.0 - uses: "docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee" # v4.2.0 with: registry: artifact.probo.inc username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_PASSWORD }} - uses: "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8 with: name: "binary-linux-${{ matrix.arch }}" path: "linux/${{ matrix.arch }}" - name: "Prepare binaries" run: "chmod +x linux/${{ matrix.arch }}/*" - name: "Compute image tag" id: tag run: | VERSION="${GITHUB_REF_NAME##*/v}" echo "image_tag=v${VERSION}" >> "$GITHUB_OUTPUT" - name: "Build and push Docker image" env: PLATFORM: ${{ matrix.platform }} ARCH: ${{ matrix.arch }} IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | docker buildx build \ --platform "${PLATFORM}" \ --build-arg "BASE_IMAGE=artifact.probo.inc/dockerhub/library/ubuntu@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b" \ --label "org.opencontainers.image.title=probod" \ --label "org.opencontainers.image.description=Probo compliance management platform" \ --label "org.opencontainers.image.url=https://github.com/getprobo/probo" \ --label "org.opencontainers.image.source=https://github.com/getprobo/probo" \ --label "org.opencontainers.image.version=${GITHUB_REF_NAME}" \ --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ --label "org.opencontainers.image.licenses=MIT" \ --tag "artifact.probo.inc/probo/probo:${IMAGE_TAG}-${ARCH}" \ --push \ . docker-manifest: name: "docker-manifest" needs: [build-docker] runs-on: "runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache" permissions: packages: "write" id-token: "write" outputs: digest: ${{ steps.digest.outputs.digest }} image_tag: ${{ steps.tag.outputs.image_tag }} steps: - uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2 - uses: "docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" # v4.1.0 - uses: "docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee" # v4.2.0 with: registry: artifact.probo.inc username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_PASSWORD }} - uses: "sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6" # v4.1.2 - name: "Log in to Harbor with cosign" env: HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }} HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }} run: | echo "${HARBOR_PASSWORD}" | cosign login artifact.probo.inc \ --username "${HARBOR_USERNAME}" \ --password-stdin - name: "Compute image tag" id: tag run: | VERSION="${GITHUB_REF_NAME##*/v}" echo "image_tag=v${VERSION}" >> "$GITHUB_OUTPUT" - name: "Create and push manifest" env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | docker buildx imagetools create \ --tag "artifact.probo.inc/probo/probo:${IMAGE_TAG}" \ --tag "artifact.probo.inc/probo/probo:latest" \ "artifact.probo.inc/probo/probo:${IMAGE_TAG}-amd64" \ "artifact.probo.inc/probo/probo:${IMAGE_TAG}-arm64" - name: "Get manifest digest" id: digest env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | DIGEST=$(docker buildx imagetools inspect "artifact.probo.inc/probo/probo:${IMAGE_TAG}" --format '{{json .Manifest.Digest}}' | jq -r) if [ -z "${DIGEST}" ] || [ "${DIGEST}" = "null" ]; then echo "Failed to resolve manifest digest for artifact.probo.inc/probo/probo:${IMAGE_TAG}" exit 1 fi echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" - name: "Sign Docker manifest" env: DIGEST: ${{ steps.digest.outputs.digest }} IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | IMAGE="artifact.probo.inc/probo/probo" cosign sign "${IMAGE}@${DIGEST}" --yes cosign sign "${IMAGE}:${IMAGE_TAG}" --yes cosign sign "${IMAGE}:latest" --yes - name: "Verify Docker signature" env: DIGEST: ${{ steps.digest.outputs.digest }} run: | cosign verify \ --certificate-identity-regexp '^https://github.com/getprobo/probo/.github/workflows/release-probod.yaml@refs/tags/probod/v' \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ "artifact.probo.inc/probo/probo@${DIGEST}" github-release: name: "github-release" needs: [build-binary, docker-manifest] runs-on: "runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache" permissions: contents: "write" id-token: "write" attestations: "write" security-events: "write" steps: - uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6 with: fetch-depth: 0 submodules: recursive - uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2 - uses: "sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6" # v4.1.2 - uses: "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8 with: pattern: "archive-*" path: "archives" merge-multiple: true - name: "Generate checksums and sign" run: | cd archives sha256sum *.tar.gz *.zip > checksums.txt cosign sign-blob --bundle="checksums.txt.bundle" checksums.txt --yes - name: "Generate SBOM" uses: "anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610" # v0.24.0 with: path: ./ format: cyclonedx-json output-file: sbom.json - name: "Run vulnerability scan" uses: "anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2" # v7.4.0 with: sbom: "sbom.json" fail-build: true severity-cutoff: critical - name: "Attest SBOM for archives" uses: "actions/attest@a1948c3f048ba23858d222213b7c278aabede763" # v4.1.1 with: subject-path: "archives/*.tar.gz, archives/*.zip" sbom-path: "sbom.json" - name: "Attest build provenance for archives" uses: "actions/attest@a1948c3f048ba23858d222213b7c278aabede763" # v4.1.1 with: subject-path: "archives/*.tar.gz, archives/*.zip" - name: "Attest Docker image SBOM" uses: "actions/attest@a1948c3f048ba23858d222213b7c278aabede763" # v4.1.1 with: subject-name: "artifact.probo.inc/probo/probo" subject-digest: ${{ needs.docker-manifest.outputs.digest }} sbom-path: "sbom.json" - name: "Trivy scan Docker image" uses: "aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25" # 0.36.0 with: image-ref: "artifact.probo.inc/probo/probo:${{ needs.docker-manifest.outputs.image_tag }}" format: "sarif" output: "trivy-results.sarif" exit-code: 1 ignore-unfixed: true vuln-type: "os,library" severity: "CRITICAL,HIGH" - name: "Upload Trivy scan results" uses: "github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e" # v4.36.2 if: always() with: sarif_file: "trivy-results.sarif" ref: "refs/heads/main" sha: ${{ github.sha }} category: "trivy-docker-image" - name: "Compose release notes" env: IMAGE_TAG: ${{ needs.docker-manifest.outputs.image_tag }} run: | VERSION="${GITHUB_REF_NAME##*/v}" awk -v ver="$VERSION" ' /^## \[/ { if (found) exit; if ($0 ~ "\\[" ver "\\]") found=1 } found ' cmd/probod/CHANGELOG.md > release-notes.md cat >> release-notes.md << EOF ## Docker Images - \`artifact.probo.inc/probo/probo:${IMAGE_TAG}\` (multi-arch: linux/amd64, linux/arm64) - \`artifact.probo.inc/probo/probo:latest\` (multi-arch: linux/amd64, linux/arm64) EOF - name: "Create GitHub release" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PRERELEASE_FLAG="" if echo "${GITHUB_REF_NAME}" | grep -qE '(alpha|beta|rc)'; then PRERELEASE_FLAG="--prerelease" fi gh release delete "${GITHUB_REF_NAME}" --yes 2>/dev/null || true gh release create "${GITHUB_REF_NAME}" \ --title "${GITHUB_REF_NAME}" \ --notes-file release-notes.md \ $PRERELEASE_FLAG \ archives/* sbom.json - uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7 with: name: "sbom" path: "sbom.json" retention-days: 30