Przeglądaj źródła

CI: Add new reusable workflows for GitHub Actions

PatTheMav 2 lat temu
rodzic
commit
96a48e86c3

+ 58 - 0
.github/actions/compatibility-validator/action.yaml

@@ -0,0 +1,58 @@
+name: Compatibility Data Validator
+description: Checks Windows compatibility data files
+inputs:
+  repositorySecret:
+    description: GitHub token for API access
+    required: true
+  workingDirectory:
+    description: Working directory for checks
+    required: false
+    default: ${{ github.workspace }}
+runs:
+  using: composite
+  steps:
+    - name: Check Runner Operating System 🏃‍♂️
+      if: runner.os == 'Windows'
+      shell: bash
+      run: |
+        : Check Runner Operating System 🏃‍♂️
+        echo "services-validation action requires a macOS-based or Linux-based runner."
+        exit 2
+
+    - name: Install and Configure Python 🐍
+      shell: bash
+      run: |
+        : Install and Configure Python 🐍
+        if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
+
+        echo ::group::Python Set Up
+        if [[ "${RUNNER_OS}" == Linux ]]; then
+          eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
+          echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
+        fi
+        brew install --quiet python3
+        python3 -m pip install jsonschema json_source_map
+        echo ::endgroup::
+
+    - name: Validate Compatibility Files JSON Schema 🕵️
+      shell: bash
+      working-directory: ${{ inputs.workingDirectory }}
+      run: |
+        : Validate services file JSON schema 🕵️
+        if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
+        shopt -s extglob
+
+        echo ::group::Schema Validation
+        python3 -u \
+          .github/scripts/utils.py/check-jsonschema.py \
+          --loglevel INFO \
+          plugins/win-capture/data/@(compatibility|package).json
+        echo ::endgroup::
+
+    - name: Annotate Schema Validation Errors 🏷️
+      uses: yuzutech/[email protected]
+      if: failure()
+      with:
+        repo-token: ${{ inputs.repositorySecret }}
+        title: Compatibility JSON Errors
+        input: ${{ inputs.workingDirectory }}/validation_errors.json

+ 346 - 0
.github/workflows/build-project.yaml

@@ -0,0 +1,346 @@
+name: Build Project
+on:
+  workflow_call:
+jobs:
+  check-event:
+    name: Check GitHub Event Data 📡
+    runs-on: ubuntu-22.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@v3
+        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 --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: Build for macOS 🍏
+    runs-on: macos-13
+    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@v3
+        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 14.3.1 and AppleScript'
+          sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer
+          sudo sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db \
+            "INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
+          sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db \
+            "INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
+          print '::endgroup::'
+
+          print '::group::Clean Homebrew Environment'
+          local -a to_remove=()
+
+          for formula (curl) {
+            if [[ -d ${HOMEBREW_PREFIX}/opt/${formula} ]] to_remove+=(${formula})
+          }
+
+          if (( #to_remove )) brew uninstall --ignore-dependencies ${to_remove}
+          print '::endgroup::'
+
+          local -A arch_names=(x86_64 intel arm64 apple)
+          print "cpuName=${arch_names[${{ matrix.target }}]}" >> $GITHUB_OUTPUT
+
+      - uses: actions/cache/restore@v3
+        id: ccache-cache
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
+          restore-keys: |
+            ${{ runner.os }}-ccache-${{ 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 }}
+        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 }}
+
+      - 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@v3
+        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@v3
+        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@v3
+        if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
+
+  ubuntu-build:
+    name: Build for Ubuntu 🐧
+    runs-on: ubuntu-22.04
+    needs: check-event
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+          fetch-depth: 0
+
+      - uses: actions/cache/restore@v3
+        id: ccache-cache
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
+          restore-keys: |
+            ${{ runner.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@v3
+        if: ${{ ! always() }}
+        with:
+          name: obs-studio-*-sources-${{ needs.check-event.outputs.commitHash }}
+          path: ${{ github.workspace }}/build_x86_64/obs-studio-*-sources.*
+
+      - name: Upload Artifacts 📡
+        uses: actions/upload-artifact@v3
+        with:
+          name: obs-studio-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}
+          path: ${{ github.workspace }}/build_x86_64/obs-studio-*-x86_64-linux-gnu.*
+
+      - name: Upload Debug Symbol Artifacts 🪲
+        uses: actions/upload-artifact@v3
+        if: ${{ fromJSON(needs.check-event.outputs.package) }}
+        with:
+          name: obs-studio-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
+          path: ${{ github.workspace }}/build_x86_64/obs-studio-*-x86_64-linux-gnu-dbgsym.ddeb
+
+      - uses: actions/cache/save@v3
+        if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
+
+  flatpak-build:
+    name: Build Application for Flatpak 📦
+    runs-on: ubuntu-22.04
+    needs: check-event
+    defaults:
+      run:
+        shell: bash
+    container:
+      image: bilelmoussaoui/flatpak-github-actions:kde-6.4
+      options: --privileged
+    steps:
+      - uses: actions/checkout@v3
+        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}"
+
+          echo '::group::Install GitHub CLI tool'
+          dnf install -y -q gh
+          gh extension install actions/gh-actions-cache
+          echo '::endgroup::'
+
+          cache_key='flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}'
+          cache_ref='master'
+          read -r key size unit _ ref _ <<< \
+            "$(gh actions-cache list -B ${cache_ref} --key "${cache_key}-x86_64" | head -1)"
+
+          if [[ "${key}" ]]; then
+            echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
+            echo "cacheHit=true" >> $GITHUB_OUTPUT
+          else
+            echo "cacheHit=false" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Build Flatpak Manifest 🧾
+        uses: flatpak/flatpak-github-actions/flatpak-builder@v5
+        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 }}
+
+  windows-build:
+    name: Build for Windows 🪟
+    runs-on: windows-2022
+    needs: check-event
+    defaults:
+      run:
+        shell: pwsh
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+          fetch-depth: 0
+
+      - uses: actions/cache@v3
+        id: ccache-cache
+        if: github.event_name == 'pull_request'
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
+          restore-keys: |
+            ${{ runner.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 }}
+          GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }}
+        with:
+          target: x64
+          config: ${{ needs.check-event.outputs.config }}
+
+      - name: Package OBS Studio 📀
+        uses: ./.github/actions/package-obs
+        with:
+          target: x64
+          config: ${{ needs.check-event.outputs.config }}
+          package: ${{ fromJSON(needs.check-event.outputs.package) }}
+
+      - name: Upload Artifacts 📡
+        uses: actions/upload-artifact@v3
+        with:
+          name: obs-studio-windows-x64-${{ needs.check-event.outputs.commitHash }}
+          path: ${{ github.workspace }}/build_x64/obs-studio-*-windows-x64.zip

+ 63 - 0
.github/workflows/check-format.yaml

@@ -0,0 +1,63 @@
+name: Check Code Formatting 🛠️
+on:
+  workflow_call:
+jobs:
+  clang-format:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        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-13
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - name: swift-format Check 🔥
+        id: swift-format
+        uses: ./.github/actions/run-swift-format
+        with:
+          failCondition: error
+
+  cmake-format:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - name: cmake-format Check 🎛️
+        id: cmake-format
+        uses: ./.github/actions/run-cmake-format
+        with:
+          failCondition: error
+
+  flatpak-validator:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        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-22.04
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - name: Qt XML Check 🖼️
+        id: qt-xml-check
+        uses: ./.github/actions/qt-xml-validator
+        with:
+          failCondition: error

+ 146 - 0
.github/workflows/dispatch.yaml

@@ -0,0 +1,146 @@
+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
+      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
+permissions:
+  contents: write
+jobs:
+  services-validation:
+    name: Check Services Configuration Files 🕵️
+    if: github.repository_owner == 'obsproject' && inputs.job == 'services'
+    runs-on: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+      - name: Check for Defunct Services 📉
+        uses: ./.github/actions/services-validator
+        with:
+          repositorySecret: ${{ secrets.GITHUB_TOKEN }}
+          runSchemaChecks: true
+          runServiceChecks: true
+          createPullRequest: true
+
+  download-language-files:
+    name: Download Language Files 🌐
+    if: github.repository_owner == 'obsproject' && inputs.job == 'translations'
+    runs-on: ubuntu-22.04
+    env:
+      CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+          token: ${{ secrets.CROWDIN_SYNC_GITHUB_PAT }}
+      - uses: obsproject/obs-crowdin-sync/[email protected]
+
+  steam-upload:
+    name: Upload Steam Builds 🚂
+    if: github.repository_owner == 'obsproject' && inputs.job == 'steam'
+    runs-on: macos-13
+    steps:
+      - uses: actions/checkout@v3
+      - 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-22.04
+    steps:
+      - uses: actions/checkout@v3
+      - name: Get Commit Information 🆔
+        id: setup
+        run: |
+          : Get Commit Hash 🆔
+          echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
+      - uses: ./.github/actions/generate-docs
+        with:
+          commitHash: ${{ steps.checks.setup.commitHash }}
+
+  update-documentation-cloudflare:
+    name: Update Documentation for Cloudflare ☁️
+    if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+      - name: Get Commit Information 🆔
+        id: setup
+        run: |
+          : Get Commit Hash 🆔
+          echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
+      - uses: ./.github/actions/generate-docs
+        with:
+          commitHash: ${{ steps.checks.setup.commitHash }}
+          disableLinkExtensions: true
+
+  deploy-documentation:
+    name: Deploy Documentation to Cloudflare ☁️
+    if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
+    runs-on: ubuntu-22.04
+    needs: update-documentation-cloudflare
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - name: Get Commit Information 🆔
+        id: setup
+        run: |
+          : Get Commit Hash 🆔
+          echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
+
+      - uses: actions/download-artifact@v3
+        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@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 }}'

+ 94 - 0
.github/workflows/pr-pull.yaml

@@ -0,0 +1,94 @@
+name: Pull Request
+run-name: ${{ github.event.pull_request.title }} pull request run 🚀
+on:
+  workflow_dispatch:
+  pull_request:
+    paths-ignore:
+      - '**.md'
+    branches: [master]
+    types: [ opened, synchronize, reopened, labeled, unlabeled ]
+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: Check Formatting 🔍
+    uses: ./.github/workflows/check-format.yaml
+    permissions:
+      contents: read
+
+  build-project:
+    name: Build Project 🧱
+    uses: ./.github/workflows/build-project.yaml
+    secrets: inherit
+    permissions:
+      contents: read
+
+  compatibility-validation:
+    name: Validate Compatibility Data 🕵️
+    if: github.base_ref == 'master'
+    runs-on: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+        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 Data 🕵️
+    if: github.base_ref == 'master'
+    runs-on: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+        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-22.04
+    steps:
+      - uses: actions/checkout@v3
+        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)

+ 150 - 0
.github/workflows/publish.yaml

@@ -0,0 +1,150 @@
+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-22.04
+    outputs:
+      validTag: ${{ steps.check.outputs.validTag }}
+      flatpakMatrix: ${{ steps.check.outputs.flatpakMatrix }}
+    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 'flatpakMatrix=["beta", "stable"]' >> $GITHUB_OUTPUT
+              ;;
+            +([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
+              echo 'validTag=true' >> $GITHUB_OUTPUT
+              echo 'flatpakMatrix=["beta"]' >> $GITHUB_OUTPUT
+              ;;
+            *) echo 'validTag=false' >> $GITHUB_OUTPUT ;;
+          esac
+
+  flatpak-publish:
+    name: Publish to Flathub 📦
+    needs: check-tag
+    if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
+    runs-on: ubuntu-22.04
+    defaults:
+      run:
+        shell: bash
+    env:
+      FLATPAK_BUILD_PATH: flatpak_app/files/share
+    container:
+      image: bilelmoussaoui/flatpak-github-actions:kde-6.4
+      options: --privileged
+    strategy:
+      matrix:
+        branch: ${{ fromJSON(needs.check-tag.outputs.flatpakMatrix) }}
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+          fetch-depth: 0
+          set-safe-directory: ${{ 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}"
+
+          dnf install -y -q gh
+          gh extension install actions/gh-actions-cache
+
+          cache_key='flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}'
+          cache_ref='master'
+          read -r key size unit _ ref _ <<< \
+            "$(gh actions-cache list -B ${cache_ref} --key "${cache_key}-x86_64" | head -1)"
+
+          if [[ "${key}" ]]; then
+            echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
+            echo "cacheHit=true" >> $GITHUB_OUTPUT
+          else
+            echo "cacheHit=false" >> $GITHUB_OUTPUT
+          fi
+
+          echo "commitHash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
+
+      - name: Build Flatpak Manifest
+        uses: flatpak/flatpak-github-actions/flatpak-builder@v5
+        with:
+          bundle: obs-studio-${{ steps.setup.outputs.commitHash }}.flatpak
+          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/repo/screenshots
+          branch: ${{ matrix.branch }}
+
+      - name: Validate AppStream
+        working-directory: ${{ env.FLATPAK_BUILD_PATH }}
+        run: |
+          : Validate AppStream
+          appstream-util validate appdata/com.obsproject.Studio.appdata.xml
+
+      - name: Verify Icon and Metadata in app-info
+        working-directory: ${{ env.FLATPAK_BUILD_PATH }}
+        run: |
+          : Verify Icon and Metadata in app-info
+          test -f app-info/icons/flatpak/128x128/com.obsproject.Studio.png || { echo "Missing 128x128 icon in app-info!"; exit 1; }
+          test -f app-info/xmls/com.obsproject.Studio.xml.gz || { echo "Missing com.obsproject.Studio.xml.gz in app-info!"; exit 1; }
+
+      - name: Commit Screenshots to OSTree Repository
+        run: |
+          : Commit Screenshots to OSTree Repository
+          ostree commit --repo=repo --canonical-permissions --branch=screenshots/x86_64 flatpak_app/screenshots
+
+      - name: Publish to Flathub Beta
+        uses: flatpak/flatpak-github-actions/flat-manager@v5
+        if: ${{ matrix.branch == 'beta' }}
+        with:
+          flat-manager-url: https://hub.flathub.org/
+          repository: beta
+          token: ${{ secrets.FLATHUB_BETA_TOKEN }}
+
+      - name: Publish to Flathub
+        uses: flatpak/flatpak-github-actions/flat-manager@v5
+        if: ${{ matrix.branch == 'stable' }}
+        with:
+          flat-manager-url: https://hub.flathub.org/
+          repository: stable
+          token: ${{ secrets.FLATHUB_TOKEN }}
+
+  steam-upload:
+    name: Upload Steam Builds 🚂
+    needs: check-tag
+    if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
+    runs-on: macos-13
+    steps:
+      - uses: actions/checkout@v3
+      - uses: ./.github/actions/steam-upload
+        with:
+          steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
+          steamUser: ${{ secrets.STEAM_USER }}
+          steamPassword: ${{ secrets.STEAM_PASSWORD }}
+          workflowSecret: ${{ github.token }}
+          preview: false

+ 313 - 0
.github/workflows/push.yaml

@@ -0,0 +1,313 @@
+name: Push to master and release branches
+run-name: ${{ github.ref_name }} push run 🚀
+on:
+  push:
+    paths-ignore:
+      - '**.md'
+    branches:
+      - master
+      - 'release/**'
+    tags:
+      - '*'
+permissions:
+  contents: write
+concurrency:
+  group: '${{ github.workflow }} @ ${{ github.ref }}'
+  cancel-in-progress: ${{ github.ref_type == 'tag' }}
+jobs:
+  check-format:
+    name: Check Formatting 🔍
+    if: github.ref_name == 'master'
+    uses: ./.github/workflows/check-format.yaml
+    permissions:
+      contents: read
+
+  build-project:
+    name: Build Project 🧱
+    uses: ./.github/workflows/build-project.yaml
+    secrets: inherit
+    permissions:
+      contents: read
+
+  compatibility-validation:
+    name: Validate Compatibility Data 🕵️
+    if: github.ref_name == 'master'
+    runs-on: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+        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 Service Data 🕵️
+    if: github.ref_name == 'master'
+    runs-on: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+        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
+
+  upload-language-files:
+    name: Upload Language Files 🌐
+    if: github.repository_owner == 'obsproject' && github.ref_name == 'master'
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+          fetch-depth: 0
+
+      - name: Check for Changed Files ✅
+        uses: ./.github/actions/check-changes
+        id: checks
+        with:
+          baseRef: ${{ github.event.before }}
+          checkGlob: '**/en-US.ini'
+
+      - name: Upload US English Language Files 🇺🇸
+        if: fromJSON(steps.checks.outputs.hasChangedFiles)
+        uses: obsproject/obs-crowdin-sync/[email protected]
+        env:
+          CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
+          GITHUB_EVENT_BEFORE: ${{ github.event.before }}
+
+  update-documentation:
+    name: Update Documentation 📖
+    if: github.repository_owner == 'obsproject' && (github.ref_name == 'master' || github.ref_type == 'tag')
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+
+      - name: Check for Changed Files ✅
+        if: github.ref_name != '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-22.04
+    needs: update-documentation
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - name: Get Commit Information 🆔
+        id: setup
+        run: |
+          : Get Commit Hash 🆔
+          echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
+
+      - uses: actions/download-artifact@v3
+        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@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 }}'
+
+  create-appcast:
+    name: Create Sparkle Appcast 🎙️
+    if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
+    runs-on: macos-13
+    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@v3
+        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@v3
+        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@v3
+        with:
+          name: macos-sparkle-update-${{ matrix.target }}
+          path: ${{ github.workspace }}/output
+
+  create-release:
+    name: Create Release 🛫
+    if: github.ref_type == 'tag'
+    runs-on: ubuntu-22.04
+    needs: build-project
+    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@v3
+        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_x86_64_artifact_name="obs-studio-ubuntu-22.04-x86_64-${commit_hash}"
+          ubuntu_x86_64_debug_name="obs-studio-ubuntu-22.04-x86_64-${commit_hash}-dbgsym"
+
+          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_x86_64_artifact_name}/"obs-studio-*-x86_64-linux-gnu.deb \
+            "${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-x86_64.deb
+          mv -v "${ubuntu_x86_64_debug_name}/"obs-studio-*-x86_64-linux-gnu-dbgsym.ddeb \
+            "${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-x86_64-dbsym.ddeb
+          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); 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@d4e8205d7e959a9107da6396278b2f1f07af0f9b
+        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

+ 121 - 0
.github/workflows/scheduled.yaml

@@ -0,0 +1,121 @@
+name: Scheduled
+run-name: Scheduled Repository Actions ⏰
+on:
+  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: ubuntu-22.04
+    permissions:
+      checks: write
+    steps:
+      - uses: actions/checkout@v3
+        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 }}
+          runSchemaChecks: false
+          runServiceChecks: true
+          createPullRequest: true
+
+  cache-cleanup:
+    name: Cache Cleanup 🧹
+    runs-on: ubuntu-22.04
+    permissions:
+      actions: write
+    steps:
+      - name: Remove Stale Ccache Caches
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          : Remove Stale Ccache Caches
+
+          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-*")) | {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 Project 🧱
+    uses: ./.github/workflows/build-project.yaml
+    needs: cache-cleanup
+    secrets: inherit
+
+  steam-upload:
+    name: Upload Steam Builds 🚂
+    needs: [build-project]
+    if: github.repository_owner == 'obsproject'
+    runs-on: macos-13
+    defaults:
+      run:
+        shell: zsh --no-rcs --errexit --pipefail {0}
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Check Nightly Runs ☑️
+        id: checks
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          : Check Nightly Runs ☑️
+          if (( ${+RUNNER_DEBUG} )) setopt XTRACE
+
+          local last_nightly=$(gh run list --workflow scheduled.yaml --limit 1 --json headSha --jq '.[0].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' }}