diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index f18b31ef0f42c9613fff7d3801ea7ac50ad0648c..44f32c1c5d329f77b8584dbee495c1645f1a6ebd 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -12,13 +12,10 @@ jobs:
     strategy:
       matrix:
         runner: [windows-large, macos-12-xl]
-        configuration: [Release, ReleaseOS]
+        configuration: [Release]
         include:
           - runner: macos-12-xl
             developer_dir: "/Applications/Xcode_14.0.1.app/Contents/Developer"
-        exclude:
-          - runner: macos-12-xl
-            configuration: ReleaseOS
     runs-on: ${{ matrix.runner }}
     outputs:
       viewer_channel: ${{ steps.build.outputs.viewer_channel }}
@@ -100,7 +97,7 @@ jobs:
 
       - name: Determine source branch
         id: which-branch
-        uses: secondlife/viewer-build-util/which-branch@v1
+        uses: secondlife/viewer-build-util/which-branch@v2
         with:
           token: ${{ github.token }}
 
@@ -223,7 +220,7 @@ jobs:
 
       - name: Upload executable
         if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.viewer_app
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "${{ steps.build.outputs.artifact }}-app"
           path: |
@@ -233,7 +230,7 @@ jobs:
       # artifact for that too.
       - name: Upload symbol file
         if: matrix.configuration != 'ReleaseOS'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "${{ steps.build.outputs.artifact }}-symbols"
           path: |
@@ -241,7 +238,7 @@ jobs:
 
       - name: Upload metadata
         if: matrix.configuration != 'ReleaseOS'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "${{ steps.build.outputs.artifact }}-metadata"
           # emitted by build.sh, possibly multiple lines
@@ -249,7 +246,7 @@ jobs:
             ${{ steps.build.outputs.metadata }}
 
       - name: Upload physics package
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         # should only be set for viewer-private
         if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.physicstpv
         with:
@@ -270,7 +267,7 @@ jobs:
     steps:
       - name: Sign and package Windows viewer
         if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
-        uses: secondlife/viewer-build-util/sign-pkg-windows@v1
+        uses: secondlife/viewer-build-util/sign-pkg-windows@v2
         with:
           vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}"
           cert_name: "${{ env.AZURE_CERT_NAME }}"
@@ -309,7 +306,7 @@ jobs:
 
       - name: Sign and package Mac viewer
         if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team
-        uses: secondlife/viewer-build-util/sign-pkg-mac@v1
+        uses: secondlife/viewer-build-util/sign-pkg-mac@v2
         with:
           channel: ${{ needs.build.outputs.viewer_channel }}
           imagename: ${{ needs.build.outputs.imagename }}
@@ -329,7 +326,7 @@ jobs:
     steps:
       - name: Post Windows symbols
         if: env.BUGSPLAT_USER && env.BUGSPLAT_PASS
-        uses: secondlife/viewer-build-util/post-bugsplat-windows@v1
+        uses: secondlife/viewer-build-util/post-bugsplat-windows@v2
         with:
           username: ${{ env.BUGSPLAT_USER }}
           password: ${{ env.BUGSPLAT_PASS }}
@@ -346,7 +343,7 @@ jobs:
     steps:
       - name: Post Mac symbols
         if: env.BUGSPLAT_USER && env.BUGSPLAT_PASS
-        uses: secondlife/viewer-build-util/post-bugsplat-mac@v1
+        uses: secondlife/viewer-build-util/post-bugsplat-mac@v2
         with:
           username: ${{ env.BUGSPLAT_USER }}
           password: ${{ env.BUGSPLAT_PASS }}
@@ -359,31 +356,20 @@ jobs:
     runs-on: ubuntu-latest
     if: github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life_')
     steps:
-      - uses: actions/download-artifact@v3
-        with:
-          name: Windows-installer
-
-      - uses: actions/download-artifact@v3
-        with:
-          name: macOS-installer
-
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
-          name: Windows-metadata
-
-      - name: Rename windows metadata 
-        run: |
-          mv autobuild-package.xml Windows-autobuild-package.xml
-          mv newview/viewer_version.txt Windows-viewer_version.txt
+          pattern: "*-installer"
 
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
-          name: macOS-metadata
-
-      - name: Rename macOS metadata 
+          pattern: "*-metadata"
+      
+      - name: Rename metadata
         run: |
-          mv autobuild-package.xml macOS-autobuild-package.xml
-          mv newview/viewer_version.txt macOS-viewer_version.txt
+          cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml
+          cp Windows-metadata/newview/viewer_version.txt Windows-viewer_version.txt
+          cp macOS-metadata/autobuild-package.xml macOS-autobuild-package.xml
+          cp macOS-metadata/newview/viewer_version.txt macOS-viewer_version.txt
 
       # forked from softprops/action-gh-release
       - name: Create GitHub release
@@ -406,8 +392,8 @@ jobs:
           append_body: true
           fail_on_unmatched_files: true
           files: |
-            *.dmg 
-            *.exe
+            macOS-installer/*.dmg 
+            Windows-installer/*.exe
             *-autobuild-package.xml
             *-viewer_version.txt
 
diff --git a/build.sh b/build.sh
index 806718e07741c2198fcfd596a1a706de3d5b306f..46a287ea32764f95e134d1867e7e4c6ede5d59d4 100755
--- a/build.sh
+++ b/build.sh
@@ -82,7 +82,7 @@ installer_Linux()
 {
   local package_name="$1"
   local package_dir="$(build_dir_Linux)/newview/"
-  local pattern=".*$(viewer_channel_suffix ${package_name})_[0-9]+_[0-9]+_[0-9]+_[0-9]+_i686\\.tar\\.bz2\$"
+  local pattern=".*$(viewer_channel_suffix ${package_name})_[0-9]+_[0-9]+_[0-9]+_[0-9]+_i686\\.tar\\.xz\$"
   # since the additional packages are built after the base package,
   # sorting oldest first ensures that the unqualified package is returned
   # even if someone makes a qualified name that duplicates the last word of the base name
@@ -170,7 +170,7 @@ pre_build()
         # This name is consumed by indra/newview/CMakeLists.txt. Make it
         # absolute because we've had troubles with relative pathnames.
         abs_build_dir="$(cd "$build_dir"; pwd)"
-        VIEWER_SYMBOL_FILE="$(native_path "$abs_build_dir/newview/$variant/secondlife-symbols-$symplat-${AUTOBUILD_ADDRSIZE}.tar.bz2")"
+        VIEWER_SYMBOL_FILE="$(native_path "$abs_build_dir/newview/$variant/secondlife-symbols-$symplat-${AUTOBUILD_ADDRSIZE}.tar.xz")"
     fi
 
     # honor autobuild_configure_parameters same as sling-buildscripts
@@ -414,10 +414,10 @@ do
               fi
               if [ -d "$build_dir/doxygen/html" ]
               then
-                  tar -c -f "$build_dir/viewer-doxygen.tar.bz2" --strip-components 3  "$build_dir/doxygen/html"
-                  python_cmd "$helpers/codeticket.py" addoutput "Doxygen Tarball" "$build_dir/viewer-doxygen.tar.bz2" \
+                  tar -cJf "$build_dir/viewer-doxygen.tar.xz" --strip-components 3  "$build_dir/doxygen/html"
+                  python_cmd "$helpers/codeticket.py" addoutput "Doxygen Tarball" "$build_dir/viewer-doxygen.tar.xz" \
                       || fatal "Upload of doxygen tarball failed"
-                  metadata+=("$build_dir/viewer-doxygen.tar.bz2")
+                  metadata+=("$build_dir/viewer-doxygen.tar.xz")
               fi
               ;;
             *)
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 355f35c558fa67e35b4d5033fc59ca3523b0060e..39f6117c694bf792605e4570471ccc15ff22b414 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -1820,7 +1820,7 @@ if (WINDOWS)
 
     if (PACKAGE)
       add_custom_command(
-        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.bz2
+        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.xz
         COMMAND ${PYTHON_EXECUTABLE}
         ARGS
           ${CMAKE_CURRENT_SOURCE_DIR}/event_host_manifest.py
@@ -1864,7 +1864,7 @@ if (WINDOWS)
         )
         # temporarily disable packaging of event_host until hg subrepos get
         # sorted out on the parabuild cluster...
-        #${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.bz2)
+        #${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.xz)
 
     endif (PACKAGE)
 elseif (DARWIN)
@@ -1984,7 +1984,7 @@ if (LINUX)
   #endif (NOT USE_BUGSPLAT)
 
   add_custom_command(
-      OUTPUT ${product}.tar.bz2
+      OUTPUT ${product}.tar.xz
       COMMAND ${PYTHON_EXECUTABLE}
       ARGS
         ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
@@ -2038,7 +2038,7 @@ if (LINUX)
   add_custom_target(copy_l_viewer_manifest ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/.${product}.copy_touched)
 
   if (PACKAGE)
-    add_custom_target(llpackage ALL DEPENDS ${product}.tar.bz2)
+    add_custom_target(llpackage ALL DEPENDS ${product}.tar.xz)
     # Make sure we don't run two instances of viewer_manifest.py at the same time.
     add_dependencies(llpackage copy_l_viewer_manifest)
     check_message_template(llpackage)
@@ -2169,12 +2169,12 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE
         OUTPUT_VARIABLE PARENT_DIRECTORY_CYGWIN
         OUTPUT_STRIP_TRAILING_WHITESPACE)
       add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}"
-        # Use of 'tar ...j' here assumes VIEWER_SYMBOL_FILE endswith .tar.bz2;
+        # Use of 'tar ...j' here assumes VIEWER_SYMBOL_FILE endswith .tar.xz;
         # testing a string suffix is painful enough in CMake language that
         # we'll continue assuming it until forced to generalize.
         COMMAND "tar"
         ARGS
-          "cjf"
+          "cJf"
           "${VIEWER_SYMBOL_FILE_CYGWIN}"
           "-C"
           "${PARENT_DIRECTORY_CYGWIN}"
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index c7f32d0da9320f375991154df8f755764d418adb..4de4dc8fc51fe1c6a511bb62ebff9b21677ce824 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -525,7 +525,7 @@ def construct(self):
                                              'secondlife-bin.*',
                                              '*_Setup.exe',
                                              '*.bat',
-                                             '*.tar.bz2')))
+                                             '*.tar.xz')))
 
             with self.prefix(src=os.path.join(pkgdir, "VMP")):
                 # include the compiled launcher scripts so that it gets included in the file_list
@@ -1183,9 +1183,9 @@ def package_finish(self):
             # causes problems, especially with frameworks: a framework's top
             # level must contain symlinks into its Versions/Current, which
             # must itself be a symlink to some specific Versions subdir.
-            tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.bz2")
+            tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.xz")
             print(f'Creating {tarpath} from {self.get_dst_prefix()}')
-            with tarfile.open(tarpath, mode="w:bz2") as tarball:
+            with tarfile.open(tarpath, mode="w:xz") as tarball:
                 # Store in the tarball as just 'Second Life Mumble.app'
                 # instead of 'Users/someone/.../newview/Release/Second...'
                 # It's at this point that we rename 'Second Life Release.app'
@@ -1272,7 +1272,7 @@ def package_finish(self):
             self.run_command(['find', self.get_dst_prefix(),
                               '-type', 'f', '-perm', old,
                               '-exec', 'chmod', new, '{}', ';'])
-        self.package_file = installer_name + '.tar.bz2'
+        self.package_file = installer_name + '.tar.xz'
 
         # temporarily move directory tree so that it has the right
         # name in the tarfile
@@ -1285,10 +1285,10 @@ def package_finish(self):
                 # --numeric-owner hides the username of the builder for
                 # security etc.
                 self.run_command(['tar', '-C', self.get_build_prefix(),
-                                  '--numeric-owner', '-cjf',
-                                 tempname + '.tar.bz2', installer_name])
+                                  '--numeric-owner', '-cJf',
+                                 tempname + '.tar.xz', installer_name])
             else:
-                print("Skipping %s.tar.bz2 for non-Release build (%s)" % \
+                print("Skipping %s.tar.xz for non-Release build (%s)" % \
                       (installer_name, self.args['buildtype']))
         finally:
             self.run_command(["mv", tempname, realname])