7373 with :
7474 context : .
7575 file : ${{ matrix.service.dockerfile }}
76+ platforms : linux/amd64,linux/arm64
7677 push : true
7778 provenance : true
7879 sbom : true
@@ -197,6 +198,7 @@ jobs:
197198 password : ${{ secrets.DOCKERHUB_TOKEN }}
198199
199200 - name : Build and push Docker image
201+ id : build
200202 uses : docker/build-push-action@v5
201203 with :
202204 context : .
@@ -211,6 +213,24 @@ jobs:
211213 cache-from : type=gha,scope=${{ matrix.webapp.name }}
212214 cache-to : type=gha,mode=max,scope=${{ matrix.webapp.name }}
213215
216+ - name : Write digest artifact
217+ run : |
218+ mkdir -p dist
219+ cat > dist/image-${{ matrix.webapp.name }}.json <<JSON
220+ {
221+ "name": "${{ matrix.webapp.name }}",
222+ "image": "${{ env.REGISTRY }}/frameworks-${{ matrix.webapp.name }}:${{ github.ref_name }}",
223+ "digest": "${{ steps.build.outputs.digest }}",
224+ "git_commit": "${{ github.sha }}"
225+ }
226+ JSON
227+
228+ - name : Upload digest artifact
229+ uses : actions/upload-artifact@v4
230+ with :
231+ name : image-digest-${{ matrix.webapp.name }}
232+ path : dist/image-${{ matrix.webapp.name }}.json
233+
214234 - name : Upload static bundle artifact
215235 uses : actions/upload-artifact@v4
216236 with :
@@ -265,9 +285,83 @@ jobs:
265285 name : cli-${{ matrix.goos }}-${{ matrix.goarch }}
266286 path : dist/cli/*
267287
288+ build-service-binaries :
289+ name : Build Service Binaries
290+ runs-on : ubuntu-latest
291+ strategy :
292+ fail-fast : false
293+ matrix :
294+ service :
295+ - { name: gateway, context: api_gateway, cmd_path: ./cmd/bridge }
296+ - { name: quartermaster, context: api_tenants, cmd_path: ./cmd/quartermaster }
297+ - { name: commodore, context: api_control, cmd_path: ./cmd/commodore }
298+ - { name: purser, context: api_billing, cmd_path: ./cmd/purser }
299+ - { name: foghorn, context: api_balancing, cmd_path: ./cmd/foghorn }
300+ - { name: decklog, context: api_firehose, cmd_path: cmd/decklog/main.go }
301+ - { name: periscope-ingest, context: api_analytics_ingest, cmd_path: ./cmd/periscope }
302+ - { name: periscope-query, context: api_analytics_query, cmd_path: ./cmd/periscope }
303+ - { name: signalman, context: api_realtime, cmd_path: ./cmd/signalman }
304+ - { name: helmsman, context: api_sidecar, cmd_path: ./cmd/helmsman }
305+ arch :
306+ - { goos: linux, goarch: amd64 }
307+ - { goos: linux, goarch: arm64 }
308+ steps :
309+ - name : Checkout
310+ uses : actions/checkout@v4
311+
312+ - name : Setup Go
313+ uses : actions/setup-go@v5
314+ with :
315+ go-version : ' 1.22'
316+ cache-dependency-path : ${{ matrix.service.context }}/go.sum
317+
318+ - name : Read service version
319+ id : svcver
320+ run : |
321+ VERSION_FILE="${{ matrix.service.context }}/VERSION"
322+ if [[ -f "$VERSION_FILE" ]]; then
323+ echo "version=$(tr -d '\n' < "$VERSION_FILE")" >> "$GITHUB_OUTPUT"
324+ else
325+ echo "version=0.0.0" >> "$GITHUB_OUTPUT"
326+ fi
327+
328+ - name : Build binary
329+ env :
330+ GOOS : ${{ matrix.arch.goos }}
331+ GOARCH : ${{ matrix.arch.goarch }}
332+ CGO_ENABLED : 0
333+ run : |
334+ set -euo pipefail
335+ cd ${{ matrix.service.context }}
336+
337+ BINARY_NAME="frameworks-${{ matrix.service.name }}-${{ matrix.arch.goos }}-${{ matrix.arch.goarch }}"
338+ OUTPUT="frameworks-${{ matrix.service.name }}-${{ github.ref_name }}-${{ matrix.arch.goos }}-${{ matrix.arch.goarch }}"
339+
340+ go build -ldflags "\
341+ -X frameworks/pkg/version.Version=${{ github.ref_name }} \
342+ -X frameworks/pkg/version.GitCommit=${{ github.sha }} \
343+ -X frameworks/pkg/version.BuildDate=$(date -u '+%Y-%m-%dT%H:%M:%SZ') \
344+ -X frameworks/pkg/version.ComponentName=${{ matrix.service.name }} \
345+ -X frameworks/pkg/version.ComponentVersion=${{ steps.svcver.outputs.version }} \
346+ -s -w" \
347+ -o "${BINARY_NAME}" \
348+ ${{ matrix.service.cmd_path }}
349+
350+ # Create output directory
351+ mkdir -p ../dist/binaries/${{ matrix.service.name }}
352+
353+ # Package binary with platform version in filename
354+ tar czf "../dist/binaries/${{ matrix.service.name }}/${OUTPUT}.tar.gz" "${BINARY_NAME}"
355+
356+ - name : Upload binary artifact
357+ uses : actions/upload-artifact@v4
358+ with :
359+ name : binary-${{ matrix.service.name }}-${{ matrix.arch.goos }}-${{ matrix.arch.goarch }}
360+ path : dist/binaries/${{ matrix.service.name }}/*.tar.gz
361+
268362 manifest :
269363 name : Generate & Publish Manifest
270- needs : [build-images, build-webapps, build-cli]
364+ needs : [build-images, build-webapps, build-cli, build-service-binaries ]
271365 runs-on : ubuntu-latest
272366 steps :
273367 - uses : actions/checkout@v4
@@ -277,22 +371,134 @@ jobs:
277371 pattern : image-digest-*
278372 path : dist/digests
279373 merge-multiple : true
374+
375+ - name : Fetch MistServer digest from GHCR
376+ id : mistserver
377+ env :
378+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
379+ run : |
380+ set -euo pipefail
381+
382+ # Query GHCR API for latest MistServer digest
383+ DIGEST=$(curl -sL \
384+ -H "Authorization: Bearer ${GITHUB_TOKEN}" \
385+ -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
386+ "https://ghcr.io/v2/livepeer-frameworks/mistserver/manifests/latest" \
387+ | jq -r '.config.digest // empty')
388+
389+ if [[ -z "$DIGEST" ]]; then
390+ echo "WARNING: Could not fetch MistServer digest from GHCR, using placeholder"
391+ DIGEST="sha256:TODO-fetch-failed"
392+ fi
393+
394+ echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
395+ echo "Fetched MistServer digest: ${DIGEST}"
396+
280397 - name : Generate manifest
281398 run : |
282- echo "platform_version: ${{ github.ref_name }}" > dist/manifest.yaml
283- echo "git_commit: ${{ github.sha }}" >> dist/manifest.yaml
284- echo "release_date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> dist/manifest.yaml
399+ set -euo pipefail
400+
401+ # Header
402+ cat > dist/manifest.yaml <<YAML
403+ platform_version: ${{ github.ref_name }}
404+ git_commit: ${{ github.sha }}
405+ release_date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
406+
407+ YAML
408+
409+ # Services (microservices with service_version field)
285410 echo "services:" >> dist/manifest.yaml
286411 for f in dist/digests/*.json; do
287412 name=$(jq -r .name "$f")
413+ # Skip webapp/website - they go in interfaces section
414+ if [[ "$name" == "webapp" || "$name" == "website" ]]; then
415+ continue
416+ fi
417+
288418 image=$(jq -r .image "$f")
289419 digest=$(jq -r .digest "$f")
290420 svcver=$(jq -r .service_version "$f")
291- echo " - name: ${name}" >> dist/manifest.yaml
292- echo " service_version: ${svcver}" >> dist/manifest.yaml
293- echo " image: ${image}" >> dist/manifest.yaml
294- echo " digest: ${digest}" >> dist/manifest.yaml
421+
422+ cat >> dist/manifest.yaml <<YAML
423+ - name: ${name}
424+ service_version: ${svcver}
425+ image: ${image}
426+ digest: ${digest}
427+ YAML
428+ done
429+
430+ # Native Binaries (for bare-metal deployment)
431+ echo "" >> dist/manifest.yaml
432+ echo "native_binaries:" >> dist/manifest.yaml
433+ for f in dist/digests/*.json; do
434+ name=$(jq -r .name "$f")
435+ # Skip webapp/website - they don't have native binaries
436+ if [[ "$name" == "webapp" || "$name" == "website" ]]; then
437+ continue
438+ fi
439+
440+ svcver=$(jq -r .service_version "$f")
441+
442+ cat >> dist/manifest.yaml <<YAML
443+ - name: ${name}
444+ service_version: ${svcver}
445+ deployment_method: systemd
446+ artifacts:
447+ - arch: linux-amd64
448+ file: frameworks-${name}-${{ github.ref_name }}-linux-amd64.tar.gz
449+ - arch: linux-arm64
450+ file: frameworks-${name}-${{ github.ref_name }}-linux-arm64.tar.gz
451+ YAML
452+ done
453+
454+ # Interfaces (webapp/website reference implementations)
455+ echo "" >> dist/manifest.yaml
456+ echo "interfaces:" >> dist/manifest.yaml
457+ for f in dist/digests/*.json; do
458+ name=$(jq -r .name "$f")
459+ # Only include webapp/website
460+ if [[ "$name" != "webapp" && "$name" != "website" ]]; then
461+ continue
462+ fi
463+
464+ image=$(jq -r .image "$f")
465+ digest=$(jq -r .digest "$f")
466+
467+ # Determine build dir and bundle name
468+ if [[ "$name" == "webapp" ]]; then
469+ bundle="webapp-build.tar.gz"
470+ else
471+ bundle="website-build.tar.gz"
472+ fi
473+
474+ cat >> dist/manifest.yaml <<YAML
475+ - name: ${name}
476+ type: reference-implementation
477+ deployment_options: [docker, systemd]
478+ image: ${image}
479+ digest: ${digest}
480+ static_bundle: ${bundle}
481+ note: "White-label example - fork and customize for production deployments"
482+ YAML
295483 done
484+
485+ # External Dependencies (MistServer from separate repo)
486+ echo "" >> dist/manifest.yaml
487+ cat >> dist/manifest.yaml <<YAML
488+ external_dependencies:
489+ - name: mistserver
490+ repository: github.com/Livepeer-FrameWorks/mistserver
491+ image: ghcr.io/livepeer-frameworks/mistserver:latest
492+ digest: "${{ steps.mistserver.outputs.digest }}"
493+ compatibility_level: backwards-compatible
494+ required_by: [helmsman]
495+ deployment_options: [native-preferred, docker]
496+ note: "Built from separate fork - pinning optional due to backwards compatibility"
497+ YAML
498+
499+ # Infrastructure Requirements (from config/infrastructure.yaml)
500+ echo "" >> dist/manifest.yaml
501+ cat config/infrastructure.yaml >> dist/manifest.yaml
296502 - name : Upload manifest artifact
297503 uses : actions/upload-artifact@v4
298504 with :
@@ -304,6 +510,12 @@ jobs:
304510 pattern : cli-*
305511 path : dist/cli
306512 merge-multiple : true
513+ - name : Download service binary artifacts
514+ uses : actions/download-artifact@v4
515+ with :
516+ pattern : binary-*
517+ path : dist/binaries
518+ merge-multiple : true
307519 - name : Download webapp bundle
308520 uses : actions/download-artifact@v4
309521 with :
@@ -323,6 +535,7 @@ jobs:
323535 files : |
324536 dist/manifest.yaml
325537 dist/cli/*
538+ dist/binaries/*/*.tar.gz
326539 dist/webapp-build.tar.gz
327540 dist/website-build.tar.gz
328541 env :
@@ -369,25 +582,44 @@ jobs:
369582 # Commit and push
370583 git config user.name "github-actions"
371584 git config user.email "[email protected] " 372- git checkout -b "${RELEASE_BRANCH}"
585+
586+ # Fetch branch if it exists
587+ git fetch origin "${RELEASE_BRANCH}" || true
588+
589+ # Create or reset to new content
590+ git checkout -B "${RELEASE_BRANCH}"
373591 git add releases/${{ github.ref_name }}.yaml channels/${channel}.yaml
374592 git commit -m "Release manifest for ${{ github.ref_name }}"
375- git push origin "${RELEASE_BRANCH}"
593+ git push --force origin "${RELEASE_BRANCH}"
376594
377- - name : Create PR to env repo
595+ - name : Create or update PR to env repo
378596 env :
379597 GH_TOKEN : ${{ secrets.GITOPS_REPO_TOKEN }}
380598 run : |
381599 cd env-repo
382- gh pr create \
600+
601+ # Check if PR already exists
602+ PR_NUMBER=$(gh pr list \
383603 --repo "${{ env.ENV_REPO }}" \
384- --base main \
385604 --head "${RELEASE_BRANCH}" \
386- --title "Release ${{ github.ref_name }} manifest" \
387- --body "This PR adds the release manifest for ${{ github.ref_name }}.
605+ --base main \
606+ --json number \
607+ --jq '.[0].number' 2>/dev/null || echo "")
608+
609+ if [[ -n "$PR_NUMBER" ]]; then
610+ echo "PR #${PR_NUMBER} already exists and was updated via branch push"
611+ echo "https://github.com/${{ env.ENV_REPO }}/pull/${PR_NUMBER}"
612+ else
613+ gh pr create \
614+ --repo "${{ env.ENV_REPO }}" \
615+ --base main \
616+ --head "${RELEASE_BRANCH}" \
617+ --title "Release ${{ github.ref_name }} manifest" \
618+ --body "This PR adds the release manifest for ${{ github.ref_name }}.
388619
389620 It maps each service to image@digest and includes component versions.
390621
391622 **Platform Version:** \`${{ github.ref_name }}\`
392623 **Git Commit:** \`${{ github.sha }}\`
393624 **Release Date:** \`$(date -u '+%Y-%m-%dT%H:%M:%SZ')\`"
625+ fi
0 commit comments