Files
probo/.github/workflows/make.yaml
Sacha Al Himdani f7070c43a4 Serialize gqlgen codegen to fix build cache race
Parallel gqlgen instances race on the Go build cache, causing
Rewriter.getSource() to panic with "slice bounds out of range"
when go/packages returns empty source. Chain gqlgen targets with
order-only prerequisites in the Makefile and switch the CI build
job to sequential generation.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-04-20 16:33:58 +02:00

435 lines
17 KiB
YAML

name: "make"
on:
push:
branches:
- "main"
pull_request:
branches:
- "main"
permissions:
contents: "read"
jobs:
# ── Snapshot: build frontend apps ──────────────────────────────────
build-apps:
name: "build-apps"
if: github.event_name == 'push'
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # 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 trust"
run: |
npm --workspace @probo/trust run check
NODE_ENV=production npm --workspace @probo/trust run build
- uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7
with:
name: "frontend-apps"
path: |
apps/console/dist/
apps/trust/dist/
packages/emails/dist/
retention-days: 1
# ── Snapshot: build Go binaries (matrix by GOOS/GOARCH) ────────────
build-snapshot-binary:
name: "binary (${{ matrix.goos }}/${{ matrix.goarch }})"
if: github.event_name == 'push'
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # 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/console/v1
go generate ./pkg/server/api/trust/v1
go generate ./pkg/server/api/mcp/v1
- name: "Build binaries"
env:
CGO_ENABLED: "0"
GOOS: "${{ matrix.goos }}"
GOARCH: "${{ matrix.goarch }}"
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
go build -ldflags "-s -w -X 'main.version=snapshot' -X 'main.env=prod'" \
-gcflags="-e" -o "dist/probod${EXT}" ./cmd/probod/main.go
go build -ldflags "-s -w" \
-gcflags="-e" -o "dist/probod-bootstrap${EXT}" ./cmd/probod-bootstrap/main.go
go build -ldflags "-s -w -X 'main.version=snapshot'" \
-gcflags="-e" -o "dist/prb${EXT}" ./cmd/prb/main.go
- uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7
with:
name: "binary-${{ matrix.goos }}-${{ matrix.goarch }}"
path: "dist/"
retention-days: 1
# ── Snapshot: build Docker images (matrix by architecture) ─────────
build-snapshot-docker:
name: "docker (${{ matrix.arch }})"
if: github.event_name == 'push'
needs: [build-snapshot-binary]
runs-on: "runs-on=${{ github.run_id }}/runner=${{ matrix.runner }}/extras=s3-cache"
permissions:
contents: "read"
security-events: "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@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd" # v4.0.0
- uses: "docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2" # v4.0.0
with:
username: "gearnode"
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- 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: "Build Docker image"
run: |
docker buildx build \
--platform "${{ matrix.platform }}" \
--tag "ghcr.io/getprobo/probo:snapshot-${{ matrix.arch }}" \
--load \
.
- name: "Cache Trivy database"
uses: "runs-on/cache@a5f51d6f3fece787d03b7b4e981c82538a0654ed" # v4
with:
path: ~/.cache/trivy
key: "trivy-db-${{ matrix.arch }}-${{ github.run_id }}"
restore-keys: "trivy-db-${{ matrix.arch }}-"
- name: "Scan Docker image with Trivy"
uses: "aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1" # 0.35.0
with:
image-ref: "ghcr.io/getprobo/probo:snapshot-${{ matrix.arch }}"
format: "sarif"
output: "trivy-results.sarif"
exit-code: 0
ignore-unfixed: true
vuln-type: "os,library"
severity: "CRITICAL,HIGH"
cache-dir: ~/.cache/trivy
- name: "Upload Trivy scan results"
uses: "github/codeql-action/upload-sarif@6bc82e05fd0ea64601dd4b465378bbcf57de0314" # v4.32.1
with:
sarif_file: "trivy-results.sarif"
category: "trivy-${{ matrix.arch }}"
# ── Snapshot: SBOM & vulnerability scan ────────────────────────────
snapshot-scan:
name: "snapshot-scan"
if: github.event_name == 'push'
needs: [build-snapshot-binary]
runs-on: "runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610" # v0.24.0
with:
path: ./
format: cyclonedx-json
output-file: sbom.json
- uses: "anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2" # v7.4.0
with:
sbom: "sbom.json"
fail-build: true
severity-cutoff: critical
output-format: table
# ── Build (PR + push validation) ───────────────────────────────────
build:
name: "build"
runs-on: "runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "./.github/actions/setup"
with:
node: "false"
- name: "Create placeholder dist files"
run: |
mkdir -p apps/console/dist apps/trust/dist packages/emails/dist
echo dev-server > apps/console/dist/index.html
echo dev-server > apps/trust/dist/index.html
echo dev-server > packages/emails/dist/placeholder
- name: "Generate Go code"
run: |
go generate ./pkg/server/api/connect/v1
go generate ./pkg/server/api/console/v1
go generate ./pkg/server/api/trust/v1
go generate ./pkg/server/api/mcp/v1
- name: "Build binaries"
env:
CGO_ENABLED: "0"
run: |
pids=()
go build -ldflags "-s -w -X 'main.version=snapshot' -X 'main.env=prod'" \
-gcflags="-e" -o bin/probod ./cmd/probod/main.go & pids+=($!)
go build -ldflags "-s -w" \
-gcflags="-e" -o bin/probod-bootstrap ./cmd/probod-bootstrap/main.go & pids+=($!)
go build -ldflags "-s -w -X 'main.version=snapshot'" \
-gcflags="-e" -o bin/prb ./cmd/prb/main.go & pids+=($!)
for pid in "${pids[@]}"; do wait "$pid"; done
lint-go:
name: "lint-go"
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
pull-requests: "write"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "./.github/actions/setup"
with:
node: "false"
- uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
install-only: true
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
- name: "Create placeholder dist files"
run: |
mkdir -p apps/console/dist apps/trust/dist packages/emails/dist
echo dev-server > apps/console/dist/index.html
echo dev-server > apps/trust/dist/index.html
echo dev-server > packages/emails/dist/placeholder
- name: "Generate Go code"
run: |
go generate ./pkg/server/api/connect/v1
go generate ./pkg/server/api/console/v1
go generate ./pkg/server/api/trust/v1
go generate ./pkg/server/api/mcp/v1
- name: "Run gofmt"
run: |
output="$(gofmt -l apps cmd packages pkg e2e)"
if [ -n "$output" ]; then
echo "error: 'gofmt' found unformatted files:"
echo "$output"
exit 1
fi
- name: "Run go fix"
run: |
output="$(CGO_ENABLED=0 go fix -diff -omitzero=false ./apps/... ./cmd/... ./packages/... ./pkg/... ./e2e/...)"
if [ -n "$output" ]; then
echo "error: 'go fix' suggests changes; please apply them"
echo "$output"
exit 1
fi
- name: "Run golangci-lint"
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
golangci-lint run --out-format=line-number ./... 2>&1 | \
reviewdog -f=golangci-lint -reporter=github-pr-review -filter-mode=nofilter -fail-level=error -name="golangci-lint"
else
golangci-lint run ./...
fi
lint-js:
name: "lint-js"
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
pull-requests: "write"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "./.github/actions/setup"
with:
go: "false"
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
- name: "Generate Relay artifacts"
run: make relay
- name: "Run eslint"
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
for dir in apps/console apps/trust packages/ui packages/eslint-config; do
(cd "$dir" && npx eslint . --concurrency 4 --format stylish 2>/dev/null) | \
reviewdog -f=eslint -reporter=github-pr-review -filter-mode=nofilter -fail-level=error -name="eslint ($dir)"
done
(cd packages/n8n-node && npx n8n-node lint)
else
npm run lint
fi
test:
name: "test"
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "./.github/actions/setup"
- name: "Create placeholder dist files"
run: |
mkdir -p apps/console/dist apps/trust/dist
echo dev-server > apps/console/dist/index.html
echo dev-server > apps/trust/dist/index.html
- run: "npm --workspace @probo/emails run build"
- run: "make test"
env:
GOTESTSUM_JUNITFILE: "junit.xml"
- name: "Upload test results"
uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7
if: "always()"
continue-on-error: true
with:
name: "junit-results"
path: "junit.xml"
retention-days: 30
- run: "go tool cover -html=coverage.out -o coverage.html"
- uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7
continue-on-error: true
with:
name: "coverage-reports"
path: |
coverage.out
coverage.html
retention-days: 30
test-e2e:
name: "test-e2e"
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
with:
submodules: recursive
- uses: "runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0" # v2
- uses: "./.github/actions/setup"
- uses: "docker/setup-compose-action@8cccb8c14b6500aaffebff1aa49c502c34d2e5e6" # v2.1.0
- uses: "docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2" # v4.0.0
with:
username: "gearnode"
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: "Install mkcert"
run: |
go install filippo.io/mkcert@latest
sudo mkcert -install 2>&1 | grep -v 'no Firefox and/or Chrome/Chromium security databases found' || true
- name: "Cache Docker images"
id: docker-cache
uses: "runs-on/cache@a5f51d6f3fece787d03b7b4e981c82538a0654ed" # v4
with:
path: /tmp/docker-images
key: "docker-images-${{ hashFiles('compose.yaml') }}"
- name: "Load cached Docker images"
if: steps.docker-cache.outputs.cache-hit == 'true'
run: "docker load -i /tmp/docker-images/images.tar"
- name: "Pull and save Docker images"
if: steps.docker-cache.outputs.cache-hit != 'true'
run: |
docker compose pull
mkdir -p /tmp/docker-images
docker save $(docker compose config --images) -o /tmp/docker-images/images.tar
- name: "Build and start stack in parallel"
run: |
make stack-up &
STACK_PID=$!
SKIP_APPS=1 make bin/probod
wait $STACK_PID
- run: "make stack-ps"
- name: "Inject root CA into e2e config"
run: |
python3 << 'EOF'
import yaml
with open('compose/pebble/certs/rootCA.pem', 'r') as f:
root_ca = f.read()
with open('e2e/console/testdata/config.yaml', 'r') as f:
config = yaml.safe_load(f)
config['probod']['custom-domains']['acme']['root-ca'] = root_ca
with open('e2e/console/testdata/config.yaml', 'w') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
EOF
- name: "Run e2e tests"
env:
PROBO_E2E_BINARY: "${{ github.workspace }}/bin/probod"
PROBO_E2E_CONFIG: "${{ github.workspace }}/e2e/console/testdata/config.yaml"
GOTESTSUM_FORMAT: "testname"
GOTESTSUM_JUNITFILE: "junit-e2e.xml"
run: "CGO_ENABLED=1 go tool gotestsum -- -race -cover -coverprofile=coverage.out -count=1 ./e2e/console/..."
- name: "Upload test results"
uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7
if: "always()"
continue-on-error: true
with:
name: "junit-e2e-results"
path: "junit-e2e.xml"
retention-days: 30