Skip to content

Release Platform

Release Platform #8

Workflow file for this run

name: Release Platform
on:
push:
tags:
- 'v*'
permissions:
contents: write # create release, upload assets
packages: write # push images to GHCR
env:
REGISTRY: ghcr.io/livepeer-frameworks
jobs:
build-images:
name: Build & Push Images
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service:
- { name: gateway, context: api_gateway, dockerfile: api_gateway/Dockerfile }
- { name: quartermaster, context: api_tenants, dockerfile: api_tenants/Dockerfile }
- { name: commodore, context: api_control, dockerfile: api_control/Dockerfile }
- { name: purser, context: api_billing, dockerfile: api_billing/Dockerfile }
- { name: foghorn, context: api_balancing, dockerfile: api_balancing/Dockerfile }
- { name: decklog, context: api_firehose, dockerfile: api_firehose/Dockerfile }
- { name: periscope-ingest, context: api_analytics_ingest, dockerfile: api_analytics_ingest/Dockerfile }
- { name: periscope-query, context: api_analytics_query, dockerfile: api_analytics_query/Dockerfile }
- { name: signalman, context: api_realtime, dockerfile: api_realtime/Dockerfile }
- { name: helmsman, context: api_sidecar, dockerfile: api_sidecar/Dockerfile }
- { name: forms-api, context: api_forms, dockerfile: api_forms/Dockerfile }
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Read service component version (optional)
id: svcver
run: |
VERSION_FILE="${{ matrix.service.context }}/VERSION"
PACKAGE_JSON="${{ matrix.service.context }}/package.json"
if [[ -f "$VERSION_FILE" ]]; then
echo "version=$(tr -d '\n' < "$VERSION_FILE")" >> "$GITHUB_OUTPUT"
elif [[ -f "$PACKAGE_JSON" ]]; then
echo "version=$(jq -r .version "$PACKAGE_JSON")" >> "$GITHUB_OUTPUT"
else
echo "version=0.0.0" >> "$GITHUB_OUTPUT"
fi
- name: Build & push ${{ matrix.service.name }}
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.service.dockerfile }}
push: true
provenance: true
sbom: true
build-args: |
VERSION=${{ github.ref_name }}
GIT_COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.repository.updated_at }}
COMPONENT_NAME=${{ matrix.service.name }}
COMPONENT_VERSION=${{ steps.svcver.outputs.version }}
tags: |
${{ env.REGISTRY }}/frameworks-${{ matrix.service.name }}:${{ github.ref_name }}
livepeerframeworks/frameworks-${{ matrix.service.name }}:${{ github.ref_name }}
cache-from: type=gha,scope=${{ matrix.service.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.service.name }}
- name: Write digest artifact
run: |
mkdir -p dist
cat > dist/image-${{ matrix.service.name }}.json <<JSON
{
"name": "${{ matrix.service.name }}",
"image": "${{ env.REGISTRY }}/frameworks-${{ matrix.service.name }}:${{ github.ref_name }}",
"digest": "${{ steps.build.outputs.digest }}",
"service_version": "${{ steps.svcver.outputs.version }}",
"git_commit": "${{ github.sha }}"
}
JSON
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: image-digest-${{ matrix.service.name }}
path: dist/image-${{ matrix.service.name }}.json
build-webapps:
name: Build Web Applications
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
webapp:
- { name: webapp, context: website_application, env_prefix: VITE, build_dir: build }
- { name: website, context: website_marketing, env_prefix: VITE, build_dir: dist }
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build player package
run: |
cd npm_player
pnpm run build
- name: Build application
env:
# webapp (website_application) variables
VITE_AUTH_URL: ${{ secrets.VITE_AUTH_URL }}
VITE_GRAPHQL_HTTP_URL: ${{ secrets.VITE_GRAPHQL_HTTP_URL }}
VITE_GRAPHQL_WS_URL: ${{ secrets.VITE_GRAPHQL_WS_URL }}
VITE_RTMP_DOMAIN: ${{ secrets.VITE_RTMP_DOMAIN }}
VITE_HTTP_DOMAIN: ${{ secrets.VITE_HTTP_DOMAIN }}
VITE_CDN_DOMAIN: ${{ secrets.VITE_CDN_DOMAIN }}
VITE_RTMP_PATH: ${{ secrets.VITE_RTMP_PATH }}
VITE_HLS_PATH: ${{ secrets.VITE_HLS_PATH }}
VITE_WEBRTC_PATH: ${{ secrets.VITE_WEBRTC_PATH }}
VITE_EMBED_PATH: ${{ secrets.VITE_EMBED_PATH }}
VITE_MARKETING_SITE_URL: ${{ secrets.VITE_MARKETING_SITE_URL }}
VITE_TURNSTILE_AUTH_SITE_KEY: ${{ secrets.VITE_TURNSTILE_AUTH_SITE_KEY }}
# website (website_marketing) variables
VITE_APP_URL: ${{ secrets.VITE_APP_URL }}
VITE_CONTACT_API_URL: ${{ secrets.VITE_CONTACT_API_URL }}
VITE_GATEWAY_URL: ${{ secrets.VITE_GATEWAY_URL }}
VITE_GITHUB_URL: ${{ secrets.VITE_GITHUB_URL }}
VITE_LIVEPEER_URL: ${{ secrets.VITE_LIVEPEER_URL }}
VITE_LIVEPEER_EXPLORER_URL: ${{ secrets.VITE_LIVEPEER_EXPLORER_URL }}
VITE_CONTACT_EMAIL: ${{ secrets.VITE_CONTACT_EMAIL }}
VITE_FORUM_URL: ${{ secrets.VITE_FORUM_URL }}
VITE_DISCORD_URL: ${{ secrets.VITE_DISCORD_URL }}
VITE_DEMO_STREAM_NAME: ${{ secrets.VITE_DEMO_STREAM_NAME }}
VITE_COMPANY_NAME: ${{ secrets.VITE_COMPANY_NAME }}
VITE_DOMAIN: ${{ secrets.VITE_DOMAIN }}
VITE_TURNSTILE_FORMS_SITE_KEY: ${{ secrets.VITE_TURNSTILE_FORMS_SITE_KEY }}
run: |
cd ${{ matrix.webapp.context }}
pnpm run build
- name: Create static bundle
run: |
cd ${{ matrix.webapp.context }}
tar czf ../${{ matrix.webapp.name }}-build.tar.gz ${{ matrix.webapp.build_dir }}/
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.webapp.context }}/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
provenance: true
sbom: true
tags: |
${{ env.REGISTRY }}/frameworks-${{ matrix.webapp.name }}:${{ github.ref_name }}
livepeerframeworks/frameworks-${{ matrix.webapp.name }}:${{ github.ref_name }}
cache-from: type=gha,scope=${{ matrix.webapp.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.webapp.name }}
- name: Write digest artifact
run: |
mkdir -p dist
cat > dist/image-${{ matrix.webapp.name }}.json <<JSON
{
"name": "${{ matrix.webapp.name }}",
"image": "${{ env.REGISTRY }}/frameworks-${{ matrix.webapp.name }}:${{ github.ref_name }}",
"digest": "${{ steps.build.outputs.digest }}",
"git_commit": "${{ github.sha }}"
}
JSON
- name: Upload digest artifact
uses: actions/upload-artifact@v4
with:
name: image-digest-${{ matrix.webapp.name }}
path: dist/image-${{ matrix.webapp.name }}.json
- name: Upload static bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.webapp.name }}-bundle
path: ${{ matrix.webapp.name }}-build.tar.gz
build-cli:
name: Build CLI binaries
runs-on: ubuntu-latest
strategy:
matrix:
include:
- goos: linux
goarch: amd64
ext: ""
- goos: linux
goarch: arm64
ext: ""
- goos: darwin
goarch: amd64
ext: ""
- goos: darwin
goarch: arm64
ext: ""
steps:
- uses: actions/checkout@v4
- name: Sanitize platform tag to component version
id: compver
run: |
ver="${GITHUB_REF_NAME#v}"
echo "value=${ver}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache-dependency-path: cli/go.sum
- name: Build CLI ${{ matrix.goos }}/${{ matrix.goarch }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
set -euo pipefail
cd cli
out="frameworks-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}"
go build -ldflags "-X frameworks/pkg/version.Version=${{ github.ref_name }} -X frameworks/pkg/version.GitCommit=${{ github.sha }} -X frameworks/pkg/version.BuildDate=$(date -u '+%Y-%m-%dT%H:%M:%SZ') -X frameworks/pkg/version.ComponentName=cli -X frameworks/pkg/version.ComponentVersion=${{ steps.compver.outputs.value }}" -o "$out" .
cd ..
mkdir -p dist/cli
mv "cli/$out" "dist/cli/$out"
- name: Upload CLI artifacts
uses: actions/upload-artifact@v4
with:
name: cli-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/cli/*
manifest:
name: Generate & Publish Manifest
needs: [build-images, build-webapps, build-cli]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download image digest artifacts
uses: actions/download-artifact@v4
with:
pattern: image-digest-*
path: dist/digests
merge-multiple: true
- name: Fetch MistServer digest from GHCR
id: mistserver
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# Query GHCR API for latest MistServer digest
DIGEST=$(curl -sL \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://ghcr.io/v2/livepeer-frameworks/mistserver/manifests/latest" \
| jq -r '.config.digest // empty')
if [[ -z "$DIGEST" ]]; then
echo "WARNING: Could not fetch MistServer digest from GHCR, using placeholder"
DIGEST="sha256:TODO-fetch-failed"
fi
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
echo "Fetched MistServer digest: ${DIGEST}"
- name: Generate manifest
run: |
set -euo pipefail
# Header
cat > dist/manifest.yaml <<YAML
platform_version: ${{ github.ref_name }}
git_commit: ${{ github.sha }}
release_date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
YAML
# Services (microservices with service_version field)
echo "services:" >> dist/manifest.yaml
for f in dist/digests/*.json; do
name=$(jq -r .name "$f")
# Skip webapp/website - they go in interfaces section
if [[ "$name" == "webapp" || "$name" == "website" ]]; then
continue
fi
image=$(jq -r .image "$f")
digest=$(jq -r .digest "$f")
svcver=$(jq -r .service_version "$f")
cat >> dist/manifest.yaml <<YAML
- name: ${name}
service_version: ${svcver}
image: ${image}
digest: ${digest}
YAML
done
# Interfaces (webapp/website reference implementations)
echo "" >> dist/manifest.yaml
echo "interfaces:" >> dist/manifest.yaml
for f in dist/digests/*.json; do
name=$(jq -r .name "$f")
# Only include webapp/website
if [[ "$name" != "webapp" && "$name" != "website" ]]; then
continue
fi
image=$(jq -r .image "$f")
digest=$(jq -r .digest "$f")
# Determine build dir and bundle name
if [[ "$name" == "webapp" ]]; then
bundle="webapp-build.tar.gz"
else
bundle="website-build.tar.gz"
fi
cat >> dist/manifest.yaml <<YAML
- name: ${name}
type: reference-implementation
deployment_options: [docker, systemd]
image: ${image}
digest: ${digest}
static_bundle: ${bundle}
note: "White-label example - fork and customize for production deployments"
YAML
done
# External Dependencies (MistServer from separate repo)
echo "" >> dist/manifest.yaml
cat >> dist/manifest.yaml <<YAML
external_dependencies:
- name: mistserver
repository: github.com/Livepeer-FrameWorks/mistserver
image: ghcr.io/livepeer-frameworks/mistserver:latest
digest: "${{ steps.mistserver.outputs.digest }}"
compatibility_level: backwards-compatible
required_by: [helmsman]
deployment_options: [native-preferred, docker]
note: "Built from separate fork - pinning optional due to backwards compatibility"
YAML
# Infrastructure Requirements (from config/infrastructure.yaml)
echo "" >> dist/manifest.yaml
cat config/infrastructure.yaml >> dist/manifest.yaml
- name: Upload manifest artifact
uses: actions/upload-artifact@v4
with:
name: release-manifest
path: dist/manifest.yaml
- name: Download CLI artifacts
uses: actions/download-artifact@v4
with:
pattern: cli-*
path: dist/cli
merge-multiple: true
- name: Download webapp bundle
uses: actions/download-artifact@v4
with:
name: webapp-bundle
path: dist/
- name: Download website bundle
uses: actions/download-artifact@v4
with:
name: website-bundle
path: dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/manifest.yaml
dist/cli/*
dist/webapp-build.tar.gz
dist/website-build.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update-env:
name: Update GitOps Environment Repo
needs: [manifest]
runs-on: ubuntu-latest
env:
ENV_REPO: Livepeer-FrameWorks/gitops
RELEASE_BRANCH: release/${{ github.ref_name }}
steps:
- name: Checkout env repo
uses: actions/checkout@v4
with:
repository: ${{ env.ENV_REPO }}
token: ${{ secrets.GITOPS_REPO_TOKEN }}
path: env-repo
- name: Download manifest artifact
uses: actions/download-artifact@v4
with:
name: release-manifest
path: dist
- name: Commit and push manifest to env repo
run: |
set -euo pipefail
cd env-repo
# Create release manifest
mkdir -p releases
cp ../dist/manifest.yaml releases/${{ github.ref_name }}.yaml
# Update channel pointer
channel="stable"
if [[ "${{ github.ref_name }}" == *"-rc."* ]]; then channel="rc"; fi
mkdir -p channels
cat > channels/${channel}.yaml <<YAML
platform_version: ${{ github.ref_name }}
manifest: releases/${{ github.ref_name }}.yaml
updated_at: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
YAML
# Commit and push
git config user.name "github-actions"
git config user.email "[email protected]"
git checkout -b "${RELEASE_BRANCH}"
git add releases/${{ github.ref_name }}.yaml channels/${channel}.yaml
git commit -m "Release manifest for ${{ github.ref_name }}"
git push origin "${RELEASE_BRANCH}"
- name: Create PR to env repo
env:
GH_TOKEN: ${{ secrets.GITOPS_REPO_TOKEN }}
run: |
cd env-repo
gh pr create \
--repo "${{ env.ENV_REPO }}" \
--base main \
--head "${RELEASE_BRANCH}" \
--title "Release ${{ github.ref_name }} manifest" \
--body "This PR adds the release manifest for ${{ github.ref_name }}.
It maps each service to image@digest and includes component versions.
**Platform Version:** \`${{ github.ref_name }}\`
**Git Commit:** \`${{ github.sha }}\`
**Release Date:** \`$(date -u '+%Y-%m-%dT%H:%M:%SZ')\`"