obsproject/obs-studio
9 workflows · maturity 83% · 9 patterns · GitHub ↗
Practices
✓ Matrix✓ Permissions✓ Security scan○ AI review✓ Cache✓ Concurrency✓ Reusable workflows
Detected patterns
Security dimensions
Tools: github/codeql-action/upload-sarif
Workflows (9)
analyze-project security .github/workflows/analyze-project.yaml
View raw YAML
name: Analyze Project
on:
workflow_call:
jobs:
windows:
name: Windows 🪟 (PVS-Studio)
runs-on: windows-2022
if: false && github.repository_owner == 'obsproject'
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }}
with:
target: x64
config: Debug
- name: Run PVS-Studio Analysis
uses: ./.github/actions/windows-analysis
with:
pvsUsername: ${{ secrets.PVS_NAME }}
pvsKey: ${{ secrets.PVS_KEY }}
target: x64
config: Debug
macos:
name: macOS 🍏 (clang-analyze)
runs-on: macos-15
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
print '::group::Enable Xcode 16.4'
sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer
print '::endgroup::'
print '::group::Clean Homebrew Environment'
local -a unwanted_formulas=()
local -a remove_formulas=()
for formula (${unwanted_formulas}) {
if [[ -d ${HOMEBREW_PREFIX}/Cellar/${formula} ]] remove_formulas+=(${formula})
}
if (( #remove_formulas )) brew uninstall --ignore-dependencies ${remove_formulas}
print '::endgroup::'
- name: Set Up Code Signing 🔑
uses: ./.github/actions/setup-macos-codesigning
id: codesign
with:
codesignIdentity: ${{ secrets.MACOS_SIGNING_IDENTITY }}
codesignCertificate: ${{ secrets.MACOS_SIGNING_CERT }}
certificatePassword: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
keychainPassword: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
provisioningProfile: ${{ secrets.MACOS_SIGNING_PROVISIONING_PROFILE }}
notarizationUser: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
notarizationPassword: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Build OBS Studio 🧱
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
CODESIGN_IDENT: ${{ steps.codesign.outputs.codesignIdent }}
CODESIGN_TEAM: ${{ steps.codesign.outputs.codesignTeam }}
PROVISIONING_PROFILE: ${{ steps.codesign.outputs.provisioningProfileUUID }}
run: |
: Run macOS Build
local -a build_args=(
--config Debug
--target macos-arm64
--codesign
--analyze
)
if (( ${+RUNNER_DEBUG} )) build_args+=(--debug)
git fetch origin --no-tags --no-recurse-submodules -q
.github/scripts/build-macos ${build_args}
- name: Compile Analytics Data 📊
run: |
: Compile Analytics Data 📊
local analytics_root='${{ github.workspace }}/analytics'
local -a analytics_files=(${analytics_root}/StaticAnalyzer/obs-studio/**/*.plist)
for file (${analytics_files}) {
mv ${file} ${analytics_root}/${${file:t}//plist/sarif}
}
pushd ${analytics_root}
npx @microsoft/sarif-multitool merge --merge-runs *.sarif
popd
- name: Upload SARIF report files 📦
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "${{ github.workspace }}/analytics/merged.sarif"
category: 'clang-analyze (macOS Apple Silicon)'
build-project matrix .github/workflows/build-project.yaml
View raw YAML
name: Build Project
on:
workflow_call:
jobs:
check-event:
name: Event Data 🔎
runs-on: ubuntu-24.04
defaults:
run:
shell: bash
outputs:
package: ${{ steps.setup.outputs.package }}
codesign: ${{ steps.setup.outputs.codesign }}
notarize: ${{ steps.setup.outputs.notarize }}
config: ${{ steps.setup.outputs.config }}
commitHash: ${{ steps.setup.outputs.commitHash }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check Event Data ☑️
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Event Data ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
case "${GITHUB_EVENT_NAME}" in
pull_request)
config_data=('codesign:false' 'notarize:false' 'package:false' 'config:RelWithDebInfo')
if gh pr view ${{ github.event.number }} --json labels \
| jq -e -r '.labels[] | select(.name == "Seeking Testers")' > /dev/null; then
config_data[0]='codesign:true'
config_data[2]='package:true'
fi
;;
push)
config_data=('codesign:true' 'notarize:false' 'package:true' 'config:RelWithDebInfo')
if [[ ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]]; then
config_data[1]='notarize:true'
config_data[3]='config:Release'
fi
;;
workflow_dispatch)
config_data=('codesign:true' 'notarize:false' 'package:false' 'config:RelWithDebInfo')
;;
schedule)
config_data=('codesign:true' 'notarize:false' 'package:true' 'config:RelWithDebInfo')
;;
*) ;;
esac
for config in "${config_data[@]}"; do
IFS=':' read -r key value <<< "${config}"
echo "${key}=${value}" >> $GITHUB_OUTPUT
done
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
macos-build:
name: macOS 🍏
runs-on: macos-15
needs: check-event
strategy:
fail-fast: false
matrix:
target: [arm64, x86_64]
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
print '::group::Enable Xcode 26.1'
sudo xcode-select --switch /Applications/Xcode_26.1.app/Contents/Developer
print '::endgroup::'
print '::group::Clean Homebrew Environment'
local -a unwanted_formulas=()
local -a remove_formulas=()
for formula (${unwanted_formulas}) {
if [[ -d ${HOMEBREW_PREFIX}/Cellar/${formula} ]] remove_formulas+=(${formula})
}
if (( #remove_formulas )) brew uninstall --ignore-dependencies ${remove_formulas}
print '::endgroup::'
local -A arch_names=(x86_64 intel arm64 apple)
print "cpuName=${arch_names[${{ matrix.target }}]}" >> $GITHUB_OUTPUT
local xcode_cas_path="${HOME}/Library/Developer/Xcode/DerivedData/CompilationCache.noindex"
if ! [[ -d ${xcode_cas_path} ]] mkdir -p ${xcode_cas_path}
print "xcodeCasPath=${xcode_cas_path}" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v4
id: xcode-cache
with:
path: ${{ steps.setup.outputs.xcodeCasPath }}
key: ${{ runner.os }}-xcode-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-xcode-${{ matrix.target }}-
- name: Set Up Code Signing 🔑
uses: ./.github/actions/setup-macos-codesigning
if: fromJSON(needs.check-event.outputs.codesign)
id: codesign
with:
codesignIdentity: ${{ secrets.MACOS_SIGNING_IDENTITY }}
codesignCertificate: ${{ secrets.MACOS_SIGNING_CERT }}
certificatePassword: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
keychainPassword: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
provisioningProfile: ${{ secrets.MACOS_SIGNING_PROVISIONING_PROFILE }}
notarizationUser: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
notarizationPassword: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
XCODE_CAS_PATH: ${{ steps.setup.outputs.xcodeCasPath }}
with:
target: ${{ matrix.target }}
config: ${{ needs.check-event.outputs.config }}
codesign: ${{ fromJSON(needs.check-event.outputs.codesign) }}
codesignIdent: ${{ steps.codesign.outputs.codesignIdent }}
codesignTeam: ${{ steps.codesign.outputs.codesignTeam }}
provisioningProfileUUID: ${{ steps.codesign.outputs.provisioningProfileUUID }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: ${{ matrix.target }}
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
codesign: ${{ fromJSON(needs.check-event.outputs.codesign) && fromJSON(steps.codesign.outputs.haveCodesignIdent) }}
codesignIdent: ${{ steps.codesign.outputs.codesignIdent }}
notarize: ${{ fromJSON(needs.check-event.outputs.notarize) && fromJSON(steps.codesign.outputs.haveNotarizationUser) }}
codesignUser: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: obs-studio-macos-${{ matrix.target }}-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_macos/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}.*
- name: Upload Debug Symbol Artifacts 🪲
uses: actions/upload-artifact@v4
if: ${{ needs.check-event.outputs.config == 'Release' }}
with:
name: obs-studio-macos-${{ matrix.target }}-${{ needs.check-event.outputs.commitHash }}-dSYMs
path: ${{ github.workspace }}/build_macos/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}-dSYMs.tar.xz
- uses: actions/cache/save@v4
if: github.event_name != 'pull_request' && steps.xcode-cache.outputs.cache-hit != 'true'
with:
path: ${{ steps.setup.outputs.xcodeCasPath }}
key: ${{ runner.os }}-xcode-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
ubuntu-build:
name: Ubuntu 🐧
strategy:
matrix:
os: [ubuntu-24.04]
runs-on: ${{ matrix.os }}
needs: check-event
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- uses: actions/cache/restore@v4
id: ccache-cache
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
- name: Upload Source Tarball 🗜️
uses: actions/upload-artifact@v4
with:
name: obs-studio-${{ matrix.os }}-sources-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_ubuntu/obs-studio-*-sources.*
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: obs-studio-${{ matrix.os }}-x86_64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_ubuntu/obs-studio-*-x86_64-ubuntu-gnu.*
- name: Upload Debug Symbol Artifacts 🪲
uses: actions/upload-artifact@v4
if: ${{ fromJSON(needs.check-event.outputs.package) }}
with:
name: obs-studio-${{ matrix.os }}-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
path: ${{ github.workspace }}/build_ubuntu/obs-studio-*-x86_64-ubuntu-gnu-dbgsym.ddeb
- uses: actions/cache/save@v4
if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
flatpak-build:
name: Flatpak 📦
runs-on: ubuntu-24.04
needs: check-event
defaults:
run:
shell: bash
env:
FLATPAK_BUILD_SHARE_PATH: flatpak_app/files/share
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
options: --privileged
volumes:
- /usr/local/lib/android:/to_clean/android
- /opt/hostedtoolcache/CodeQL:/to_clean/codeql
- /usr/local/.ghcup:/to_clean/ghcup
- /opt/hostedtoolcache/Python:/to_clean/python
- /usr/share/swift:/to_clean/swift
- /usr/share/dotnet:/to_clean/dotnet
steps:
- name: Prepare build space
shell: bash
run: |
: Prepare build space
echo ::group::Available storage
df -h
echo ::endgroup::
echo ::group::Remove Android stuff
rm -rf /to_clean/android/*
echo ::endgroup::
echo ::group::Remove CodeQL stuff
rm -rf /to_clean/codeql/*
echo ::endgroup::
echo ::group::Remove GHCup stuff
rm -rf /to_clean/ghcup/*
echo ::endgroup::
echo ::group::Remove Python stuff
rm -rf /to_clean/python/*
echo ::endgroup::
echo ::group::Remove Swift stuff
rm -rf /to_clean/swift/*
echo ::endgroup::
echo ::group::Remove .NET stuff
rm -rf /to_clean/dotnet/*
echo ::endgroup::
echo ::group::Available storage
df -h
echo ::endgroup::
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
- name: Set Up Environment 🔧
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Set Up Environment 🔧
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
cache_key='flatpak-builder-${{ hashFiles('build-aux/com.obsproject.Studio.json') }}'
cache_ref='master'
read -r id key size unit created accessed <<< \
"$(gh cache list --ref "refs/heads/${cache_ref}" --key "${cache_key}-x86_64" | head -1)"
if [[ "${key}" ]]; then
echo "cacheHit=true" >> $GITHUB_OUTPUT
else
echo "cacheHit=false" >> $GITHUB_OUTPUT
fi
echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
- name: Validate Flatpak manifest
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: manifest
path: build-aux/com.obsproject.Studio.json
- name: Build Flatpak Manifest 🧾
uses: flatpak/flatpak-github-actions/flatpak-builder@b8a638469ea7ec62844d7b6e487b697e6f249576
with:
build-bundle: ${{ fromJSON(needs.check-event.outputs.package) }}
bundle: obs-studio-flatpak-${{ needs.check-event.outputs.commitHash }}.flatpak
manifest-path: ${{ github.workspace }}/build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cacheHit) || (github.event_name == 'push' && github.ref_name == 'master')}}
restore-cache: ${{ fromJSON(steps.setup.outputs.cacheHit) }}
cache-key: ${{ steps.setup.outputs.cacheKey }}
mirror-screenshots-url: https://dl.flathub.org/media
- name: Validate AppStream
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: appstream
path: ${{ env.FLATPAK_BUILD_SHARE_PATH }}/metainfo/com.obsproject.Studio.metainfo.xml
- name: Verify Icon and Metadata in app-info
working-directory: ${{ env.FLATPAK_BUILD_SHARE_PATH }}
run: |
: Verify Icon and Metadata in app-info
test -f app-info/icons/flatpak/128x128/com.obsproject.Studio.png || { echo "::error::Missing 128x128 icon in app-info"; exit 1; }
test -f app-info/xmls/com.obsproject.Studio.xml.gz || { echo "::error::Missing com.obsproject.Studio.xml.gz in app-info"; exit 1; }
- name: Validate build directory
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: builddir
path: flatpak_app
- name: Validate repository
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: repo
path: repo
windows-build:
name: Windows 🪟
strategy:
matrix:
architecture: [x64, arm64]
runs-on: windows-2022
needs: check-event
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }}
with:
target: ${{ matrix.architecture }}
config: ${{ needs.check-event.outputs.config }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: ${{ matrix.architecture }}
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: obs-studio-windows-${{ matrix.architecture }}-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_${{ matrix.architecture }}/obs-studio-*-windows-${{ matrix.architecture }}.zip
check-format .github/workflows/check-format.yaml
View raw YAML
name: Check Code Formatting 🛠️
on:
workflow_call:
jobs:
clang-format:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: clang-format Check 🐉
id: clang-format
uses: ./.github/actions/run-clang-format
with:
failCondition: error
swift-format:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: swift-format Check 🔥
id: swift-format
uses: ./.github/actions/run-swift-format
with:
failCondition: error
gersemi:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: gersemi Check 🎛️
id: gersemi
uses: ./.github/actions/run-gersemi
with:
failCondition: error
flatpak-validator:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Flatpak Manifest Check 📦
id: flatpak-check
uses: ./.github/actions/flatpak-manifest-validator
with:
failCondition: error
qt-xml-validator:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Qt XML Check 🖼️
id: qt-xml-check
uses: ./.github/actions/qt-xml-validator
with:
failCondition: error
dispatch perms .github/workflows/dispatch.yaml
View raw YAML
name: Dispatch
run-name: Dispatched Repository Actions - ${{ inputs.job }} ⌛️
on:
workflow_dispatch:
inputs:
job:
description: Dispatch job to run
required: true
type: choice
options:
- steam
- services
- translations
- documentation
- patches
ref:
description: GitHub reference to use for job
type: string
required: false
customAssetWindows:
description: Custom Windows build for Steam Upload
type: string
required: false
customAssetMacOSApple:
description: Custom macOS Apple Silicon build for Steam Upload
type: string
required: false
customAssetMacOSIntel:
description: Custom macOS Intel build for Steam Upload
type: string
required: false
channel:
description: Channel to use when generating Windows update files
type: string
required: false
permissions:
contents: write
jobs:
services-validation:
name: Validate Services 🕵️
if: github.repository_owner == 'obsproject' && inputs.job == 'services'
runs-on: macos-15
permissions:
checks: write
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check for Defunct Services 📉
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
checkApiSecret: ${{ secrets.CHECK_SERVERS_API_KEY }}
checkApiServers: ${{ secrets.CHECK_SERVERS_LIST }}
runSchemaChecks: true
runServiceChecks: true
createPullRequest: true
download-language-files:
name: Download Language Files 🌐
if: github.repository_owner == 'obsproject' && inputs.job == 'translations'
runs-on: ubuntu-24.04
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.CROWDIN_SYNC_GITHUB_PAT }}
fetch-depth: 0
- uses: obsproject/obs-crowdin-sync/download@430665179ed13233af2d83ec192c2ae8c40a29ae
steam-upload:
name: Upload Steam Builds 🚂
if: github.repository_owner == 'obsproject' && inputs.job == 'steam'
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/steam-upload
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
tagName: ${{ inputs.ref }}
customAssetWindows: ${{ inputs.customAssetWindows }}
customAssetMacOSApple: ${{ inputs.customAssetMacOSApple }}
customAssetMacOSIntel: ${{ inputs.customAssetMacOSIntel }}
workflowSecret: ${{ github.token }}
preview: false
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/generate-docs
update-documentation-cloudflare:
name: Update Documentation for Cloudflare ☁️
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/generate-docs
with:
disableLinkExtensions: true
deploy-documentation:
name: Deploy Documentation to Cloudflare ☁️
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-24.04
needs: update-documentation-cloudflare
defaults:
run:
shell: bash
environment:
name: cf-pages-deploy
steps:
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v4
with:
name: OBS Studio Docs (No Extensions) ${{ steps.setup.outputs.commitHash }}
path: docs
- name: Publish to Live Page
uses: cloudflare/wrangler-action@4c10c1822abba527d820b29e6333e7f5dac2cabd
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ steps.setup.outputs.commitHash }}'
windows-patches:
name: Create Windows Patches 🩹
if: github.repository_owner == 'obsproject' && inputs.job == 'patches'
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/windows-patches
with:
tagName: ${{ inputs.ref }}
workflowSecret: ${{ github.token }}
channel: ${{ inputs.channel }}
gcsAccessKeyId: ${{ secrets.GCS_ACCESS_KEY_ID }}
gcsAccessKeySecret: ${{ secrets.GCS_ACCESS_KEY_SECRET }}
pr-pull perms .github/workflows/pr-pull.yaml
View raw YAML
name: Pull
run-name: ${{ github.event.pull_request.title }} pull request run 🚀
on:
workflow_dispatch:
pull_request:
paths-ignore:
- '**.md'
branches: [master]
types: [ opened, synchronize, reopened ]
permissions:
contents: read
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
check-format:
name: Format 🔍
uses: ./.github/workflows/check-format.yaml
permissions:
contents: read
build-project:
name: Build 🧱
uses: ./.github/workflows/build-project.yaml
secrets: inherit
permissions:
contents: read
compatibility-validation:
name: Validate Compatibility 🕵️
if: github.base_ref == 'master'
runs-on: ubuntu-24.04
permissions:
checks: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: plugins/win-capture/data/*.json
- name: Check for Invalid Compatibility Data 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/compatibility-validator
services-validation:
name: Validate Services 🕵️
if: github.base_ref == 'master'
runs-on: ubuntu-24.04
permissions:
checks: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: plugins/rtmp-services/data/*.json
- name: Check Services JSON Schema 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
runSchemaChecks: true
runServiceChecks: false
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && github.base_ref == 'master'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: docs/sphinx
- uses: ./.github/actions/generate-docs
if: fromJSON(steps.checks.outputs.hasChangedFiles)
publish matrix perms .github/workflows/publish.yaml
View raw YAML
name: Publish
run-name: Publish Repository Actions 🛫
on:
release:
types:
- published
branches:
- master
- 'release/**'
permissions:
contents: read
concurrency:
group: '${{ github.workflow }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
check-tag:
name: Check Release Tag
if: github.repository_owner == 'obsproject'
runs-on: ubuntu-24.04
outputs:
validTag: ${{ steps.check.outputs.validTag }}
flatpakMatrix: ${{ steps.check.outputs.flatpakMatrix }}
updateChannel: ${{ steps.check.outputs.updateChannel }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
- name: Check Release Tag ☑️
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Release Tag ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
case "${GITHUB_REF_NAME}" in
+([0-9]).+([0-9]).+([0-9]) )
lastPreRelease="$(gh release list --exclude-drafts --limit 10 --json "publishedAt,tagName,isPrerelease" \
--jq '[.[] | select(.isPrerelease == true)] | first | .tagName')"
currentRelease="${GITHUB_REF_NAME}"
isPreReleaseAhead=false
printf '%s\n%s\n' "${currentRelease}" "${lastPreRelease}" | sort --version-sort --reverse --check=quiet &&
isPreReleaseAhead=false || isPreReleaseAhead=true
# Edge case: Sort considers the non-suffixed version older than a suffixed one
if ${isPreReleaseAhead} && [[ "${currentRelease}" == "${lastPreRelease//-*}" ]]; then
isPreReleaseAhead=false
fi
echo 'validTag=true' >> $GITHUB_OUTPUT
if ! ${isPreReleaseAhead}; then
echo 'flatpakMatrix=["beta", "stable"]' >> $GITHUB_OUTPUT
else
echo 'flatpakMatrix=["stable"]' >> $GITHUB_OUTPUT
fi
echo 'updateChannel=stable' >> $GITHUB_OUTPUT
;;
+([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'flatpakMatrix=["beta"]' >> $GITHUB_OUTPUT
echo 'updateChannel=beta' >> $GITHUB_OUTPUT
;;
*) echo 'validTag=false' >> $GITHUB_OUTPUT ;;
esac
flatpak-publish:
name: Flathub 📦
needs: check-tag
if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
runs-on: ubuntu-24.04
defaults:
run:
shell: bash
env:
FLATPAK_BUILD_SHARE_PATH: flatpak_app/files/share
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
options: --privileged
volumes:
- /usr/local/lib/android:/to_clean/android
- /opt/hostedtoolcache/CodeQL:/to_clean/codeql
- /usr/local/.ghcup:/to_clean/ghcup
- /opt/hostedtoolcache/Python:/to_clean/python
- /usr/share/swift:/to_clean/swift
- /usr/share/dotnet:/to_clean/dotnet
strategy:
matrix:
branch: ${{ fromJSON(needs.check-tag.outputs.flatpakMatrix) }}
steps:
- name: Prepare build space
shell: bash
run: |
: Prepare build space
echo ::group::Available storage
df -h
echo ::endgroup::
echo ::group::Remove Android stuff
rm -rf /to_clean/android/*
echo ::endgroup::
echo ::group::Remove CodeQL stuff
rm -rf /to_clean/codeql/*
echo ::endgroup::
echo ::group::Remove GHCup stuff
rm -rf /to_clean/ghcup/*
echo ::endgroup::
echo ::group::Remove Python stuff
rm -rf /to_clean/python/*
echo ::endgroup::
echo ::group::Remove Swift stuff
rm -rf /to_clean/swift/*
echo ::endgroup::
echo ::group::Remove .NET stuff
rm -rf /to_clean/dotnet/*
echo ::endgroup::
echo ::group::Available storage
df -h
echo ::endgroup::
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
- name: Set Up Environment 🔧
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Set Up Environment 🔧
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
cache_key='flatpak-builder-${{ hashFiles('build-aux/com.obsproject.Studio.json') }}'
cache_ref='master'
read -r id key size unit created accessed <<< \
"$(gh cache list --ref "refs/heads/${cache_ref}" --key "${cache_key}-x86_64" | head -1)"
if [[ "${key}" ]]; then
echo "cacheHit=true" >> $GITHUB_OUTPUT
else
echo "cacheHit=false" >> $GITHUB_OUTPUT
fi
echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- name: Validate Flatpak manifest
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: manifest
path: build-aux/com.obsproject.Studio.json
- name: Build Flatpak Manifest
uses: flatpak/flatpak-github-actions/flatpak-builder@b8a638469ea7ec62844d7b6e487b697e6f249576
with:
build-bundle: false
manifest-path: ${{ github.workspace }}/build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cacheHit) }}
cache-key: ${{ steps.setup.outputs.cacheKey }}
mirror-screenshots-url: https://dl.flathub.org/media
branch: ${{ matrix.branch }}
- name: Validate AppStream
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: appstream
path: ${{ env.FLATPAK_BUILD_SHARE_PATH }}/metainfo/com.obsproject.Studio.metainfo.xml
- name: Verify Icon and Metadata in app-info
working-directory: ${{ env.FLATPAK_BUILD_SHARE_PATH }}
run: |
: Verify Icon and Metadata in app-info
test -f app-info/icons/flatpak/128x128/com.obsproject.Studio.png || { echo "::error::Missing 128x128 icon in app-info"; exit 1; }
test -f app-info/xmls/com.obsproject.Studio.xml.gz || { echo "::error::Missing com.obsproject.Studio.xml.gz in app-info"; exit 1; }
- name: Validate build directory
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: builddir
path: flatpak_app
- name: Validate repository
uses: ./.github/actions/flatpak-builder-lint
with:
artifact: repo
path: repo
- name: Publish to Flathub Beta
uses: flatpak/flatpak-github-actions/flat-manager@b8a638469ea7ec62844d7b6e487b697e6f249576
if: ${{ matrix.branch == 'beta' }}
with:
flat-manager-url: https://hub.flathub.org/
repository: beta
token: ${{ secrets.FLATHUB_BETA_TOKEN }}
build-log-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Publish to Flathub
uses: flatpak/flatpak-github-actions/flat-manager@b8a638469ea7ec62844d7b6e487b697e6f249576
if: ${{ matrix.branch == 'stable' }}
with:
flat-manager-url: https://hub.flathub.org/
repository: stable
token: ${{ secrets.FLATHUB_TOKEN }}
build-log-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steam-upload:
name: Upload Steam Builds 🚂
needs: check-tag
if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/steam-upload
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
workflowSecret: ${{ github.token }}
tagName: ${{ github.ref_name }}
preview: false
windows-patches:
name: Create Windows Patches 🩹
needs: check-tag
if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/windows-patches
with:
tagName: ${{ github.ref_name }}
workflowSecret: ${{ github.token }}
channel: ${{ needs.check-tag.outputs.updateChannel }}
gcsAccessKeyId: ${{ secrets.GCS_ACCESS_KEY_ID }}
gcsAccessKeySecret: ${{ secrets.GCS_ACCESS_KEY_SECRET }}
push matrix perms .github/workflows/push.yaml
View raw YAML
name: Push
run-name: ${{ github.ref_name }} push run 🚀
on:
push:
paths-ignore:
- '**.md'
branches:
- master
- 'release/**'
tags:
- '*'
permissions:
contents: write
jobs:
check-format:
name: Format 🔍
if: github.ref_name == 'master'
uses: ./.github/workflows/check-format.yaml
permissions:
contents: read
build-project:
name: Build 🧱
uses: ./.github/workflows/build-project.yaml
secrets: inherit
permissions:
contents: read
compatibility-validation:
name: Validate Compatibility 🕵️
if: github.ref_name == 'master'
runs-on: ubuntu-24.04
permissions:
checks: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: plugins/win-capture/data/*.json
- name: Check for Invalid Compatibility Data 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/compatibility-validator
with:
repositorySecret: ${{ github.token }}
services-validation:
name: Validate Services 🕵️
if: github.ref_name == 'master'
runs-on: ubuntu-24.04
permissions:
checks: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: plugins/rtmp-services/data/*.json
- name: Check Services JSON Schema 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ github.token }}
runSchemaChecks: true
runServiceChecks: false
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && (github.ref_name == 'master' || github.ref_type == 'tag')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Changed Files ✅
if: github.ref_type != 'tag'
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: '!(cmake*)'
- uses: ./.github/actions/generate-docs
if: github.ref_type == 'tag' || fromJSON(steps.checks.outputs.hasChangedFiles)
with:
disableLinkExtensions: ${{ github.ref_type == 'tag' }}
deploy-documentation:
name: Deploy Documentation to Cloudflare ☁️
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
runs-on: ubuntu-24.04
needs: update-documentation
defaults:
run:
shell: bash
environment:
name: cf-pages-deploy
steps:
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v4
with:
name: OBS Studio Docs (No Extensions) ${{ steps.setup.outputs.commitHash }}
path: docs
- name: Set Up Redirects 🔄
run: |
: Set Up Redirects 🔄
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
echo "/previous/27.2 https://obsproject.com/docs/27.2 302" >> docs/_redirects
echo "/previous/:major.:minor https://:major-:minor.${{ vars.CF_PAGES_PROJECT }}.pages.dev 302" >> docs/_redirects
- name: Publish to Live Page
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ steps.setup.outputs.commitHash }}'
create-appcast:
name: Create Sparkle Appcast 🎙️
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
runs-on: macos-15
needs: build-project
strategy:
fail-fast: false
matrix:
target: [arm64, x86_64]
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
ref: ${{ github.ref }}
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
local channel='stable'
if [[ ${GITHUB_REF_NAME} == *(beta|rc)* ]] {
channel='beta'
}
local -A arch_names=(x86_64 intel arm64 apple)
print "cpuName=${arch_names[${{ matrix.target }}]}" >> $GITHUB_OUTPUT
print "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
print "channel=${channel}" >> $GITHUB_OUTPUT
- name: Download Artifact 📥
uses: actions/download-artifact@v4
with:
name: obs-studio-macos-${{ matrix.target }}-${{ steps.setup.outputs.commitHash }}
- name: Generate Appcast 🎙️
id: generate-appcast
uses: ./.github/actions/sparkle-appcast
with:
sparklePrivateKey: ${{ secrets.SPARKLE_PRIVATE_KEY }}
baseImage: ${{ github.workspace }}/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}.dmg
channel: ${{ steps.setup.outputs.channel }}
count: 1
urlPrefix: 'https://cdn-fastly.obsproject.com/downloads'
customTitle: 'OBS Studio'
customLink: 'https://obsproject.com/'
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: macos-sparkle-update-${{ matrix.target }}
path: ${{ github.workspace }}/output
merge-appcasts:
runs-on: ubuntu-24.04
needs: create-appcast
steps:
- name: Merge Appcasts
uses: actions/upload-artifact/merge@v4
with:
name: macos-sparkle-update
pattern: macos-sparkle-update-*
delete-merged: true
sign-windows-build:
name: Windows Signing ✍️
uses: obsproject/obs-studio/.github/workflows/sign-windows.yaml@1933c9e3205d67068f2c858a52ec06b68e3e65ee
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
needs: build-project
permissions:
contents: 'read'
id-token: 'write'
attestations: 'write'
secrets: inherit
create-release:
name: Create Release 🛫
if: github.ref_type == 'tag'
runs-on: ubuntu-24.04
needs: [build-project, sign-windows-build]
defaults:
run:
shell: bash
steps:
- name: Check Release Tag ☑️
id: check
run: |
: Check Release Tag ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
case "${GITHUB_REF_NAME}" in
+([0-9]).+([0-9]).+([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'prerelease=false' >> $GITHUB_OUTPUT
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
;;
+([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'prerelease=true' >> $GITHUB_OUTPUT
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
;;
*) echo 'validTag=false' >> $GITHUB_OUTPUT ;;
esac
- name: Download Build Artifacts 📥
uses: actions/download-artifact@v4
if: ${{ fromJSON(steps.check.outputs.validTag) }}
- name: Rename Files 🏷️
if: fromJSON(steps.check.outputs.validTag)
run: |
: Rename Files 🏷️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
root_dir="${PWD}"
commit_hash="${GITHUB_SHA:0:9}"
macos_arm64_artifact_name="obs-studio-macos-arm64-${commit_hash}"
macos_arm64_dsym_artifact_name="obs-studio-macos-arm64-${commit_hash}-dSYMs"
macos_intel_artifact_name="obs-studio-macos-x86_64-${commit_hash}"
macos_intel_dsym_artifact_name="obs-studio-macos-x86_64-${commit_hash}-dSYMs"
ubuntu_2404_x86_64_artifact_name="obs-studio-ubuntu-24.04-x86_64-${commit_hash}"
ubuntu_2404_x86_64_debug_name="obs-studio-ubuntu-24.04-x86_64-${commit_hash}-dbgsym"
ubuntu_2404_sources_name="obs-studio-ubuntu-24.04-sources-${commit_hash}"
windows_x64_artifact_name="obs-studio-windows-x64-${{ steps.check.outputs.version }}-signed"
windows_x64_installer_name="obs-studio-windows-x64-${{ steps.check.outputs.version }}-installer"
windows_x64_debug_name="obs-studio-windows-x64-${{ steps.check.outputs.version }}-pdbs"
windows_arm64_artifact_name="obs-studio-windows-arm64-${{ steps.check.outputs.version }}-signed"
windows_arm64_debug_name="obs-studio-windows-arm64-${{ steps.check.outputs.version }}-pdbs"
echo '::group::Renaming Artifacts'
mv -v "${macos_arm64_artifact_name}/"obs-studio-*-macos-apple.dmg \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Apple.dmg
mv -v "${macos_arm64_dsym_artifact_name}/"obs-studio-*-macos-apple-dSYMs.tar.xz \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Apple-dSYMs.tar.xz
mv -v "${macos_intel_artifact_name}/"obs-studio-*-macos-intel.dmg \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Intel.dmg
mv -v "${macos_intel_dsym_artifact_name}/"obs-studio-*-macos-intel-dSYMs.tar.xz \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Intel-dSYMs.tar.xz
mv -v "${ubuntu_2404_x86_64_artifact_name}/"obs-studio-*-x86_64-ubuntu-gnu.deb \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-24.04-x86_64.deb
mv -v "${ubuntu_2404_x86_64_debug_name}/"obs-studio-*-x86_64-ubuntu-gnu-dbgsym.ddeb \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-24.04-x86_64-dbsym.ddeb
mv -v "${ubuntu_2404_sources_name}/"obs-studio-*-sources.tar.gz \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Sources.tar.gz
mv -v "${windows_x64_artifact_name}/"OBS-Studio-*.zip \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64.zip
mv -v "${windows_x64_installer_name}/"OBS-Studio-*.exe \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64-Installer.exe
mv -v "${windows_x64_debug_name}/"OBS-Studio-*-pdbs.zip \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64-PDBs.zip
mv -v "${windows_arm64_artifact_name}/"OBS-Studio-*.zip \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Windows-arm64.zip
mv -v "${windows_arm64_debug_name}/"OBS-Studio-*-pdbs.zip \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Windows-arm64-PDBs.zip
echo '::endgroup::'
- name: Generate Checksums 🪪
if: fromJSON(steps.check.outputs.validTag)
run: |
: Generate Checksums 🪪
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
echo "### Checksums" > ${{ github.workspace }}/CHECKSUMS.txt
for file in ${{ github.workspace }}/@(*.deb|*.ddeb|*.dmg|*.tar.xz|*.tar.gz|*.exe|*.zip); do
echo " ${file##*/}: $(sha256sum "${file}" | cut -d " " -f 1)" >> ${{ github.workspace }}/CHECKSUMS.txt
done
- name: Create Release 🛫
if: fromJSON(steps.check.outputs.validTag)
id: create_release
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
with:
draft: true
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}
tag_name: ${{ steps.check.outputs.version }}
name: OBS Studio ${{ steps.check.outputs.version }}
body_path: ${{ github.workspace }}/CHECKSUMS.txt
files: |
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-macOS-*.dmg
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-macOS-*-dSYMs.tar.xz
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-*.deb
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-*.ddeb
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Sources.tar.gz
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64.zip
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64-Installer.exe
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Windows-x64-PDBs.zip
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Windows-arm64.zip
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Windows-arm64-PDBs.zip
scheduled perms .github/workflows/scheduled.yaml
View raw YAML
name: Scheduled
run-name: Scheduled Repository Actions ⏰
on:
workflow_dispatch:
schedule:
- cron: 17 0 * * *
permissions:
contents: write
concurrency:
group: '${{ github.workflow }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
services-availability:
name: Check Service Availability 🛜
if: github.repository_owner == 'obsproject'
runs-on: macos-15
permissions:
checks: write
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master
- name: Check for Defunct Services 📉
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
checkApiSecret: ${{ secrets.CHECK_SERVERS_API_KEY }}
checkApiServers: ${{ secrets.CHECK_SERVERS_LIST }}
runSchemaChecks: false
runServiceChecks: true
createPullRequest: true
cache-cleanup:
name: Cache Cleanup 🧹
runs-on: ubuntu-24.04
permissions:
actions: write
steps:
- name: Remove Stale Ccache Caches
env:
GH_TOKEN: ${{ github.token }}
run: |
: Remove Stale Ccache Caches
# The jq expressions below use multiple 'select' calls to filter
# each item in the array with the 'actions_caches' key.
# First it only selects objects whose 'ref' element has the value
# 'refs/heads/master', of those objects only those whose 'key'
# value matches the specified expression, before finally only
# selecting the 'id' and 'key' elements for a new object.
# The final 'join' command combines both elements with a semicolon
# into a raw string which can then be parsed directly.
echo '::group::Processing master branch cache entries'
while IFS=";" read -r cache_id cache_name; do
if [[ "${cache_name}" ]]; then
result=true
gh api -X DELETE repos/${GITHUB_REPOSITORY}/actions/caches?key=${cache_name} --jq '.total_count' &> /dev/null || result=false
if ${result}; then
echo "Deleted cache entry ${cache_name}"
else
echo "::warning::Unable to delete cache entry ${cache_name}"
fi
fi
done <<< \
"$(gh api repos/${GITHUB_REPOSITORY}/actions/caches \
--jq '.actions_caches.[] | select(.ref|test("refs/heads/master")) | select(.key|test(".*-(ccache|xcode)-*")) | {id, key} | join(";")')"
echo '::endgroup::'
echo '::group::Processing pull request cache entries'
while IFS=";" read -r cache_id cache_name cache_ref; do
if [[ "${cache_name}" ]]; then
result=true
gh api -X DELETE repos/${GITHUB_REPOSITORY}/actions/caches?key=${cache_name} --jq '.total_count' &> /dev/null || result=false
pr_number=$(echo ${cache_ref} | cut -d '/' -f 3)
if ${result}; then
echo "Deleted PR #${pr_number} cache entry ${cache_name}"
else
echo "::warning::Unable to delete PR #${pr_number} cache entry ${cache_name}"
fi
fi
done <<< \
"$(gh api repos/${GITHUB_REPOSITORY}/actions/caches \
--jq '.actions_caches.[] | select(.ref|test("refs/heads/master")|not) | select(.key|test(".*-ccache-*")) | {id, key, ref} | join(";")')"
echo '::endgroup::'
build-project:
name: Build 🧱
uses: ./.github/workflows/build-project.yaml
needs: cache-cleanup
secrets: inherit
analyze-project:
name: Analyze 🔬
uses: ./.github/workflows/analyze-project.yaml
needs: cache-cleanup
secrets: inherit
permissions:
security-events: write
upload-language-files:
name: Upload Language Files 🌐
if: github.repository_owner == 'obsproject' && github.ref_name == 'master'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Check Nightly Runs ☑️
id: nightly-checks
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Nightly Runs ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
# This 'gh' command retrieves the last 2 runs of the workflow defined
# by 'scheduled.yaml' and retrieves just the 'headSha' value of the
# JSON response payload.
#
# As this job runs in context of the same workflow, the first element
# of the workflow list will be the currently active run.
#
# The jq expression then selects the 'headSha' element of the second
# element in the array, which is the SHA-1 hash of the commit used
# for the immediately prior run of this workflow.
last_nightly=$(gh run list --workflow scheduled.yaml --limit 2 --json headSha --jq '.[1].headSha')
if [[ "${GITHUB_SHA}" == "${last_nightly}" ]]; then
echo "passed=false" >> $GITHUB_OUTPUT
else
echo "passed=true" >> $GITHUB_OUTPUT
echo "lastNightly=${last_nightly}" >> $GITHUB_OUTPUT
fi
- name: Check for Changed Files ✅
if: fromJSON(steps.nightly-checks.outputs.passed)
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ steps.nightly-checks.outputs.lastNightly }}
checkGlob: '**/en-US.ini'
- name: Upload US English Language Files 🇺🇸
if: steps.checks.outcome == 'success' && fromJSON(steps.checks.outputs.hasChangedFiles)
uses: obsproject/obs-crowdin-sync/upload@430665179ed13233af2d83ec192c2ae8c40a29ae
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
GITHUB_EVENT_BEFORE: ${{ steps.nightly-checks.outputs.lastNightly }}
steam-upload:
name: Upload Steam Builds 🚂
needs: [build-project]
if: github.repository_owner == 'obsproject'
runs-on: macos-15
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v4
- name: Check Nightly Runs ☑️
id: checks
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Nightly Runs ☑️
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
# This 'gh' command retrieves the last 2 runs of the workflow defined
# by 'scheduled.yaml' and retrieves just the 'headSha' value of the
# JSON response payload.
#
# As this job runs in context of the same workflow, the first element
# of the workflow list will be the currently active run.
#
# The jq expression then selects the 'headSha' element of the second
# element in the array, which is the SHA-1 hash of the commit used
# for the immediately prior run of this workflow.
local last_nightly=$(gh run list --workflow scheduled.yaml --limit 2 --json headSha --jq '.[1].headSha')
if [[ "${GITHUB_SHA}" == "${last_nightly}" ]] {
print "passed=false" >> $GITHUB_OUTPUT
} else {
print "passed=true" >> $GITHUB_OUTPUT
}
- uses: ./.github/actions/steam-upload
if: fromJSON(steps.checks.outputs.passed)
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
workflowSecret: ${{ secrets.GITHUB_TOKEN }}
preview: ${{ github.repository_owner != 'obsproject' }}
sign-windows matrix .github/workflows/sign-windows.yaml
View raw YAML
name: Sign Windows Project
on:
workflow_call:
jobs:
create-windows-update:
name: Sign Windows Build 🥩
strategy:
matrix:
architecture: [x64, arm64]
runs-on: windows-2022
environment:
name: bouf
defaults:
run:
shell: pwsh
steps:
- name: Parse JWT
id: jwt
run: |
$token = ConvertTo-SecureString -String ${env:ACTIONS_ID_TOKEN_REQUEST_TOKEN} -AsPlainText
$jwt = Invoke-WebRequest -Uri "${env:ACTIONS_ID_TOKEN_REQUEST_URL}&audience=ignore" -Authentication Bearer -Token $token
$claim_b64 = (($jwt.Content | ConvertFrom-Json -AsHashtable).value -split '\.')[1]
$mod = $claim_b64.Length % 4
if ($mod -gt 0) {$claim_b64 += '=' * (4 - $mod)}
$claim = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($claim_b64)) | ConvertFrom-Json -AsHashtable
$sha = ${claim}.job_workflow_sha
Write-Output "Workflow SHA: ${sha}"
"workflow_sha=${sha}" >> $env:GITHUB_OUTPUT
- uses: actions/checkout@v4
with:
path: "repo"
fetch-depth: 0
ref: ${{ steps.jwt.outputs.workflow_sha }}
- name: Set Up Environment 🔧
id: setup
run: |
$channel = if ($env:GITHUB_REF_NAME -match "(beta|rc)") { "beta" } else { "stable" }
$shortHash = $env:GITHUB_SHA.Substring(0,9)
"channel=${channel}" >> $env:GITHUB_OUTPUT
"commitHash=${shortHash}" >> $env:GITHUB_OUTPUT
- name: Download Artifact 📥
uses: actions/download-artifact@v4
with:
name: obs-studio-windows-${{ matrix.architecture }}-${{ steps.setup.outputs.commitHash }}
path: ${{ github.workspace }}/build
- name: Run bouf 🥩
uses: ./repo/.github/actions/windows-signing
with:
gcpWorkloadIdentityProvider: ${{ secrets.GCP_IDENTITY_POOL }}
gcpServiceAccountName: ${{ secrets.GCP_SERVICE_ACCOUNT_NAME }}
version: ${{ github.ref_name }}
channel: ${{ steps.setup.outputs.channel }}
architecture: ${{ matrix.architecture }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: ${{ github.workspace }}/output/*-${{ matrix.architecture }}.zip
- name: Upload Signed Build
uses: actions/upload-artifact@v4
with:
name: obs-studio-windows-${{ matrix.architecture }}-${{ github.ref_name }}-signed
compression-level: 0
path: ${{ github.workspace }}/output/*-${{ matrix.architecture }}.zip
- name: Upload PDBs
uses: actions/upload-artifact@v4
with:
name: obs-studio-windows-${{ matrix.architecture }}-${{ github.ref_name }}-pdbs
compression-level: 0
path: ${{ github.workspace }}/output/*-pdbs.zip
- name: Upload Installer
uses: actions/upload-artifact@v4
if: matrix.architecture == 'x64'
with:
name: obs-studio-windows-${{ matrix.architecture }}-${{ github.ref_name }}-installer
compression-level: 0
path: ${{ github.workspace }}/output/*.exe