obsproject/obs-studio

9 workflows · maturity 83% · 9 patterns · GitHub ↗

Security 38.06/100

Practices

✓ Matrix✓ Permissions✓ Security scan○ AI review✓ Cache✓ Concurrency✓ Reusable workflows

Detected patterns

Security dimensions

permissions
13.9
security scan
4.2
supply chain
20
secret handling
0
harden runner
0

Tools: github/codeql-action/upload-sarif

Workflows (9)

analyze-project security .github/workflows/analyze-project.yaml
Triggers
workflow_call
Runs on
windows-2022, macos-15
Jobs
windows, macos
Actions
github/codeql-action/upload-sarif
Commands
  • : 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::'
  • : 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}
  • : 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
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
Triggers
workflow_call
Runs on
ubuntu-24.04, macos-15, ${{ matrix.os }}, ubuntu-24.04, windows-2022
Jobs
check-event, macos-build, ubuntu-build, flatpak-build, windows-build
Matrix
architecture, os, target→ arm64, ubuntu-24.04, x64, x86_64
Actions
flatpak/flatpak-github-actions/flatpak-builder
Commands
  • : 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
  • : 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
  • : 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::
  • : 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
  • : 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; }
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
Triggers
workflow_call
Runs on
ubuntu-24.04, macos-15, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04
Jobs
clang-format, swift-format, gersemi, flatpak-validator, qt-xml-validator
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
Triggers
workflow_dispatch
Runs on
macos-15, ubuntu-24.04, macos-15, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, windows-2022
Jobs
services-validation, download-language-files, steam-upload, update-documentation, update-documentation-cloudflare, deploy-documentation, windows-patches
Actions
obsproject/obs-crowdin-sync/download, cloudflare/wrangler-action
Commands
  • : Get Commit Hash 🆔 echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
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
Triggers
workflow_dispatch, pull_request
Runs on
ubuntu-24.04, ubuntu-24.04, ubuntu-24.04
Jobs
check-format, build-project, compatibility-validation, services-validation, update-documentation
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
Triggers
release
Runs on
ubuntu-24.04, ubuntu-24.04, macos-15, windows-2022
Jobs
check-tag, flatpak-publish, steam-upload, windows-patches
Matrix
branch→ ${{ fromJSON(needs.check-tag.outputs.flatpakMatrix) }}
Actions
flatpak/flatpak-github-actions/flatpak-builder, flatpak/flatpak-github-actions/flat-manager, flatpak/flatpak-github-actions/flat-manager
Commands
  • : 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
  • : 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::
  • : 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
  • : 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; }
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
Triggers
push
Runs on
ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, macos-15, ubuntu-24.04, ubuntu-24.04
Jobs
check-format, build-project, compatibility-validation, services-validation, update-documentation, deploy-documentation, create-appcast, merge-appcasts, sign-windows-build, create-release
Matrix
target→ arm64, x86_64
Actions
cloudflare/wrangler-action, actions/upload-artifact/merge, softprops/action-gh-release
Commands
  • : Get Commit Hash 🆔 echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
  • : 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
  • : 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
  • : 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
  • : 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::'
  • : 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
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
Triggers
workflow_dispatch, schedule
Runs on
macos-15, ubuntu-24.04, ubuntu-24.04, macos-15
Jobs
services-availability, cache-cleanup, build-project, analyze-project, upload-language-files, steam-upload
Actions
Homebrew/actions/setup-homebrew, obsproject/obs-crowdin-sync/upload
Commands
  • : 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::'
  • : 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
  • : 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 }
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
Triggers
workflow_call
Runs on
windows-2022
Jobs
create-windows-update
Matrix
architecture→ arm64, x64
Actions
actions/attest-build-provenance
Commands
  • $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
  • $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
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