From b9255b3896b51b9807510177faf3886cffdd1017 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 19 Dec 2025 17:48:55 -0500 Subject: [PATCH 01/23] Update action.yaml --- sign-pkg-windows/action.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index 93bb13e..a87f412 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -18,6 +18,10 @@ inputs: tenant_id: description: "Azure signer app tenantId" required: true + installer_type: + description: "Installer type: 'velopack' (default) or 'nsis'" + default: 'velopack' + required: false nsis_version: description: "NSIS version to install if not already present" default: '3.10' @@ -27,6 +31,7 @@ runs: using: composite steps: - name: Setup NSIS + if: ${{ inputs.installer_type != 'velopack' }} # Install NSIS if not already installed on the runner # See https://nsis.sourceforge.io/Download shell: pwsh @@ -92,7 +97,11 @@ runs: "$exe" done + # Velopack packaging is now handled by viewer_manifest.py during the build step + # The installer should already exist in the artifact when USE_VELOPACK=ON + - name: Build the installer + if: ${{ inputs.installer_type != 'velopack' }} id: nsis shell: python run: | @@ -128,14 +137,15 @@ runs: subprocess.check_call(command, cwd='.app') - name: Dump NSIS input file - if: ${{ failure() && steps.nsis.conclusion == 'failure' }} + if: ${{ failure() && steps.nsis.conclusion == 'failure' && inputs.installer_type != 'velopack' }} shell: bash run: cat '.app/secondlife_setup_tmp.nsi' - name: Sign the installer shell: bash run: | - installer="$(ls -t .app/*_Setup.exe | head -n 1)" + # Match both NSIS (*_Setup.exe) and Velopack (*-Setup.exe) output patterns + installer="$(ls -t .app/*Setup.exe | head -n 1)" # pass installer to next step echo "installer=$installer" >> "$GITHUB_ENV" python "${{ github.action_path }}/sign.py" \ From 2f38cbb3cb730a9e9e4aff51ae988a003afe78f8 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Thu, 12 Mar 2026 02:37:47 -0400 Subject: [PATCH 02/23] Remove SLVersionChecker from signing. --- sign-pkg-windows/action.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index a87f412..30a99b0 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -85,7 +85,6 @@ runs: shell: bash run: | for exe in .app/SecondLifeViewer.exe \ - .app/SLVersionChecker.exe \ .app/llplugin/dullahan_host.exe do python "${{ github.action_path }}/sign.py" \ From 2c924e1e273b87581d809d34f3de79c647d1c2e1 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 13 Mar 2026 18:27:46 -0400 Subject: [PATCH 03/23] Update action.yaml --- sign-pkg-windows/action.yaml | 110 +++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index 30a99b0..c4cac0a 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -26,6 +26,27 @@ inputs: description: "NSIS version to install if not already present" default: '3.10' required: true + velopack_pack_id: + description: "Velopack pack ID (from build step)" + required: false + velopack_pack_version: + description: "Velopack pack version (from build step)" + required: false + velopack_pack_title: + description: "Velopack pack title (from build step)" + required: false + velopack_main_exe: + description: "Velopack main executable name (from build step)" + required: false + velopack_exclude: + description: "Velopack exclude pattern (from build step)" + required: false + velopack_icon: + description: "Velopack icon filename in .app dir (from build step)" + required: false + velopack_installer_base: + description: "Base name for installer output files (from build step)" + required: false runs: using: composite @@ -82,6 +103,7 @@ runs: run: dotnet tool install --global AzureSignTool - name: Sign the executables + if: ${{ inputs.installer_type != 'velopack' }} shell: bash run: | for exe in .app/SecondLifeViewer.exe \ @@ -96,8 +118,75 @@ runs: "$exe" done - # Velopack packaging is now handled by viewer_manifest.py during the build step - # The installer should already exist in the artifact when USE_VELOPACK=ON + - name: Setup .NET for Velopack + if: ${{ inputs.installer_type == 'velopack' }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Install Velopack CLI + if: ${{ inputs.installer_type == 'velopack' }} + shell: bash + run: dotnet tool install -g vpk + + - name: Build and sign Velopack package + if: ${{ inputs.installer_type == 'velopack' }} + shell: bash + env: + PACK_ID: ${{ inputs.velopack_pack_id }} + PACK_VERSION: ${{ inputs.velopack_pack_version }} + PACK_TITLE: ${{ inputs.velopack_pack_title }} + MAIN_EXE: ${{ inputs.velopack_main_exe }} + EXCLUDE: ${{ inputs.velopack_exclude }} + ICON: ${{ inputs.velopack_icon }} + INSTALLER_BASE: ${{ inputs.velopack_installer_base }} + VAULT_URI: ${{ inputs.vault_uri }} + CERT_NAME: ${{ inputs.cert_name }} + CLIENT_ID: ${{ inputs.client_id }} + CLIENT_SECRET: ${{ inputs.client_secret }} + TENANT_ID: ${{ inputs.tenant_id }} + run: | + set -x + + vpk_args=( + vpk pack + --packId "$PACK_ID" + --packVersion "$PACK_VERSION" + --packDir .app + --mainExe "$MAIN_EXE" + --packTitle "$PACK_TITLE" + --exclude "$EXCLUDE" + --shortcuts '' + --signTemplate "AzureSignTool sign -kvu $VAULT_URI -kvi $CLIENT_ID -kvt $TENANT_ID -kvs $CLIENT_SECRET -kvc $CERT_NAME -tr http://timestamp.digicert.com -v {{file}}" + ) + + # Add icon if provided + if [[ -n "$ICON" && -f ".app/$ICON" ]]; then + vpk_args+=(--icon ".app/$ICON") + fi + + "${vpk_args[@]}" + + - name: Rename Velopack outputs + if: ${{ inputs.installer_type == 'velopack' }} + shell: bash + env: + PACK_ID: ${{ inputs.velopack_pack_id }} + INSTALLER_BASE: ${{ inputs.velopack_installer_base }} + run: | + # Move Setup.exe into .app for the installer upload step + setup="Releases/${PACK_ID}-win-Setup.exe" + if [[ -f "$setup" ]]; then + mv "$setup" ".app/${INSTALLER_BASE}_Setup.exe" + echo "Moved $setup to .app/${INSTALLER_BASE}_Setup.exe" + fi + + # Rename Portable.zip to include version + portable="Releases/${PACK_ID}-win-Portable.zip" + if [[ -f "$portable" ]]; then + mv "$portable" "Releases/${INSTALLER_BASE}_Portable.zip" + echo "Moved $portable to Releases/${INSTALLER_BASE}_Portable.zip" + fi - name: Build the installer if: ${{ inputs.installer_type != 'velopack' }} @@ -141,11 +230,10 @@ runs: run: cat '.app/secondlife_setup_tmp.nsi' - name: Sign the installer + if: ${{ inputs.installer_type != 'velopack' }} shell: bash run: | - # Match both NSIS (*_Setup.exe) and Velopack (*-Setup.exe) output patterns installer="$(ls -t .app/*Setup.exe | head -n 1)" - # pass installer to next step echo "installer=$installer" >> "$GITHUB_ENV" python "${{ github.action_path }}/sign.py" \ --vault_uri "${{ inputs.vault_uri }}" \ @@ -155,8 +243,22 @@ runs: --tenant_id "${{ inputs.tenant_id }}" \ "$installer" + - name: Find Velopack installer + if: ${{ inputs.installer_type == 'velopack' }} + shell: bash + run: | + installer="$(ls -t .app/*_Setup.exe | head -n 1)" + echo "installer=$installer" >> "$GITHUB_ENV" + - name: Post the installer uses: actions/upload-artifact@v4 with: name: "Windows-installer" path: ${{ env.installer }} + + - name: Upload Velopack releases + if: ${{ inputs.installer_type == 'velopack' }} + uses: actions/upload-artifact@v4 + with: + name: "Windows-releases" + path: Releases/ From 19c1c1d182e60e7d9964a6529f8824f86230813c Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 18 Mar 2026 01:13:38 -0400 Subject: [PATCH 04/23] Make sure we mirror our Windows flow for macOS with velopack. --- sign-pkg-mac/action.yaml | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 1a2f87a..8c2c4b0 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -40,6 +40,21 @@ inputs: type: string required: false default: "Second Life" + velopack_pack_id: + description: "Velopack pack ID (from build step)" + required: false + velopack_pack_version: + description: "Velopack pack version (from build step)" + required: false + velopack_pack_title: + description: "Velopack pack title (from build step)" + required: false + velopack_main_exe: + description: "Velopack main executable name (from build step)" + required: false + velopack_bundle_id: + description: "Velopack bundle ID (from build step)" + required: false runs: using: composite @@ -177,3 +192,46 @@ runs: name: "macOS-installer" path: ${{ env.installer }} + # Velopack packaging — runs after signing so the signed app bundle is packaged + - name: Setup .NET for Velopack + if: ${{ inputs.velopack_pack_id }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Install Velopack CLI + if: ${{ inputs.velopack_pack_id }} + shell: bash + run: dotnet tool install -g vpk + + - name: Build Velopack package + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + PACK_ID: ${{ inputs.velopack_pack_id }} + PACK_VERSION: ${{ inputs.velopack_pack_version }} + PACK_TITLE: ${{ inputs.velopack_pack_title }} + MAIN_EXE: ${{ inputs.velopack_main_exe }} + BUNDLE_ID: ${{ inputs.velopack_bundle_id }} + run: | + set -x + # The signed app bundle is in .app/ from the unpack step + app_bundle="$(ls -dt .app/*.app | head -n 1)" + + vpk pack \ + --packId "$PACK_ID" \ + --packVersion "$PACK_VERSION" \ + --packDir "$app_bundle" \ + --packTitle "$PACK_TITLE" \ + --mainExe "$MAIN_EXE" \ + --bundleId "$BUNDLE_ID" \ + --outputDir Releases \ + --noInst \ + --verbose + + - name: Upload Velopack releases + if: ${{ inputs.velopack_pack_id }} + uses: actions/upload-artifact@v4 + with: + name: "macOS-releases" + path: Releases/ From 0e9d9cc068e2f38625a29b1c93d841c2b4c7b8bb Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 17:38:30 -0400 Subject: [PATCH 05/23] Add setup-velopack action --- setup-velopack/action.yaml | 129 +++++++++++++++++++++++++++++++++++ sign-pkg-mac/action.yaml | 107 +++++++++++++++++++---------- sign-pkg-windows/action.yaml | 19 +++--- 3 files changed, 210 insertions(+), 45 deletions(-) create mode 100644 setup-velopack/action.yaml diff --git a/setup-velopack/action.yaml b/setup-velopack/action.yaml new file mode 100644 index 0000000..11b4ed3 --- /dev/null +++ b/setup-velopack/action.yaml @@ -0,0 +1,129 @@ +name: setup-velopack +description: + Install the Velopack CLI (vpk) and replace the bundled Setup.exe + with the patched version from our 3p-velopack autobuild package + that tolerates unknown command-line arguments from legacy launchers. + +inputs: + dotnet_version: + description: ".NET SDK version to install" + required: false + default: '9.0.x' + setup_exe_source: + description: > + Where to get the patched setup.exe (Windows only). Options: + 'auto' (default) - Download from latest 3p-velopack GitHub release + 'none' - Skip replacement, use upstream vpk's bundled setup.exe + A URL - Download from this specific URL + required: false + default: 'auto' + github_token: + description: > + GitHub token with access to secondlife-3p/3p-velopack releases. + Required when setup_exe_source is 'auto'. Falls back to github.token. + required: false + +runs: + using: composite + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_version }} + + - name: Install Velopack CLI + shell: bash + run: dotnet tool install -g vpk + + - name: Replace bundled Setup.exe with patched version + if: ${{ runner.os == 'Windows' && inputs.setup_exe_source != 'none' }} + shell: bash + env: + SETUP_EXE_SOURCE: ${{ inputs.setup_exe_source }} + GH_TOKEN: ${{ inputs.github_token || github.token }} + run: | + set -e + + # Find the vpk tool store — try both Unix and Windows-style home paths + VPK_STORE="" + for candidate in \ + "$HOME/.dotnet/tools/.store/vpk" \ + "$USERPROFILE/.dotnet/tools/.store/vpk" \ + "$(cygpath "$USERPROFILE" 2>/dev/null)/.dotnet/tools/.store/vpk" + do + if [[ -d "$candidate" ]]; then + VPK_STORE="$candidate" + break + fi + done + + if [[ -z "$VPK_STORE" ]]; then + echo "::error::vpk tool store not found" + exit 1 + fi + + # Find setup.exe in the store + BUNDLED_SETUP="$(find "$VPK_STORE" -iname 'setup.exe' -type f | head -n 1)" + if [[ -z "$BUNDLED_SETUP" ]]; then + echo "::error::Could not find setup.exe in vpk tool store at $VPK_STORE" + find "$VPK_STORE" -type f | head -20 + exit 1 + fi + echo "Found bundled setup.exe at: $BUNDLED_SETUP" + + if [[ "$SETUP_EXE_SOURCE" == "auto" ]]; then + # Fetch the latest release from 3p-velopack and find the windows64 asset + echo "Fetching latest 3p-velopack release..." + RELEASE_JSON="$(curl -sf \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GH_TOKEN" \ + "https://api.github.com/repos/secondlife-3p/3p-velopack/releases/latest")" + + if [[ -z "$RELEASE_JSON" ]]; then + echo "::warning::Could not fetch 3p-velopack releases, skipping setup.exe replacement" + exit 0 + fi + + # Find the windows64 asset API URL (use 'url' not 'browser_download_url' for auth) + ASSET_URL="$(echo "$RELEASE_JSON" | python3 -c " + import json, sys + data = json.load(sys.stdin) + for asset in data.get('assets', []): + if 'windows64' in asset['name']: + print(asset['url']) + break + ")" + + if [[ -z "$ASSET_URL" ]]; then + echo "::warning::No windows64 asset found in latest 3p-velopack release, skipping setup.exe replacement" + exit 0 + fi + + echo "Downloading 3p-velopack windows64 package..." + dl_tmpdir="$(mktemp -d)" + curl -L -f -o "$dl_tmpdir/velopack.tar.bz2" \ + -H "Accept: application/octet-stream" \ + -H "Authorization: Bearer $GH_TOKEN" \ + "$ASSET_URL" + + # Extract bin/setup.exe from the tarball + # The autobuild package stages it at bin/setup.exe + tar xjf "$dl_tmpdir/velopack.tar.bz2" -C "$dl_tmpdir" "bin/setup.exe" 2>&1 || true + + if [[ -f "$dl_tmpdir/bin/setup.exe" ]]; then + cp "$dl_tmpdir/bin/setup.exe" "$BUNDLED_SETUP" + echo "Replaced setup.exe from 3p-velopack package" + else + echo "::warning::bin/setup.exe not found in 3p-velopack package, skipping replacement" + echo "Tarball contents:" + tar tjf "$dl_tmpdir/velopack.tar.bz2" | head -20 + fi + rm -rf "$dl_tmpdir" + else + # Direct URL provided + echo "Downloading patched setup.exe from: $SETUP_EXE_SOURCE" + curl -L -f -o "$BUNDLED_SETUP" "$SETUP_EXE_SOURCE" + echo "Replaced setup.exe from URL" + fi + + ls -la "$BUNDLED_SETUP" diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 8c2c4b0..9c8246a 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -73,6 +73,76 @@ runs: mkdir -p ".app" tar xJf .tarball/* -C ".app" + # Velopack packaging — run BEFORE signing so that UpdateMac and sq.version + # are injected into the app bundle. The signed DMG will then contain a + # Velopack-ready app bundle that can self-update from day one. + - name: Setup Velopack CLI + if: ${{ inputs.velopack_pack_id }} + uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack + + - name: Build Velopack package + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + PACK_ID: ${{ inputs.velopack_pack_id }} + PACK_VERSION: ${{ inputs.velopack_pack_version }} + PACK_TITLE: ${{ inputs.velopack_pack_title }} + MAIN_EXE: ${{ inputs.velopack_main_exe }} + BUNDLE_ID: ${{ inputs.velopack_bundle_id }} + run: | + set -x + # The app bundle from the build step + app_bundle="$(ls -dt .app/*.app | head -n 1)" + + # Run vpk pack — this injects UpdateMac and sq.version into the bundle, + # then produces the nupkg and Portable.zip from it. + vpk pack \ + --packId "$PACK_ID" \ + --packVersion "$PACK_VERSION" \ + --packDir "$app_bundle" \ + --packTitle "$PACK_TITLE" \ + --mainExe "$MAIN_EXE" \ + --bundleId "$BUNDLE_ID" \ + --outputDir Releases \ + --noInst \ + --verbose + + - name: Replace app bundle with Velopack-ready version + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + PACK_ID: ${{ inputs.velopack_pack_id }} + PACK_TITLE: ${{ inputs.velopack_pack_title }} + run: | + set -x + # The Portable.zip contains the app bundle with UpdateMac and sq.version + # already injected by vpk pack. Extract it and use it for the DMG. + portable_zip="$(ls Releases/*-Portable.zip | head -n 1)" + if [[ -z "$portable_zip" ]]; then + echo "::error::No Portable.zip found in Releases/" + exit 1 + fi + + # Remove the original app bundle and extract the Velopack-ready one + rm -rf .app/*.app + # ditto preserves macOS metadata, resource forks, and symlinks + ditto -xk "$portable_zip" .app/ + + # Verify we got an app bundle + new_app="$(ls -dt .app/*.app | head -n 1)" + if [[ -z "$new_app" ]]; then + echo "::error::No .app bundle found after extracting Portable.zip" + exit 1 + fi + + echo "Using Velopack-ready app bundle: $new_app" + # Verify UpdateMac is present + if [[ -f "$new_app/Contents/MacOS/UpdateMac" ]]; then + echo "UpdateMac binary present" + else + echo "::warning::UpdateMac binary not found in extracted bundle" + fi + - name: Set up the app sparseimage shell: bash run: | @@ -192,43 +262,6 @@ runs: name: "macOS-installer" path: ${{ env.installer }} - # Velopack packaging — runs after signing so the signed app bundle is packaged - - name: Setup .NET for Velopack - if: ${{ inputs.velopack_pack_id }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '9.0.x' - - - name: Install Velopack CLI - if: ${{ inputs.velopack_pack_id }} - shell: bash - run: dotnet tool install -g vpk - - - name: Build Velopack package - if: ${{ inputs.velopack_pack_id }} - shell: bash - env: - PACK_ID: ${{ inputs.velopack_pack_id }} - PACK_VERSION: ${{ inputs.velopack_pack_version }} - PACK_TITLE: ${{ inputs.velopack_pack_title }} - MAIN_EXE: ${{ inputs.velopack_main_exe }} - BUNDLE_ID: ${{ inputs.velopack_bundle_id }} - run: | - set -x - # The signed app bundle is in .app/ from the unpack step - app_bundle="$(ls -dt .app/*.app | head -n 1)" - - vpk pack \ - --packId "$PACK_ID" \ - --packVersion "$PACK_VERSION" \ - --packDir "$app_bundle" \ - --packTitle "$PACK_TITLE" \ - --mainExe "$MAIN_EXE" \ - --bundleId "$BUNDLE_ID" \ - --outputDir Releases \ - --noInst \ - --verbose - - name: Upload Velopack releases if: ${{ inputs.velopack_pack_id }} uses: actions/upload-artifact@v4 diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index c4cac0a..a52cea6 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -47,6 +47,9 @@ inputs: velopack_installer_base: description: "Base name for installer output files (from build step)" required: false + setup_exe_url: + description: "URL to patched setup.exe for legacy launcher compatibility" + required: false runs: using: composite @@ -98,6 +101,11 @@ runs: shell: bash run: pip install pyng + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + - name: Install AzureSignTool shell: bash run: dotnet tool install --global AzureSignTool @@ -118,16 +126,11 @@ runs: "$exe" done - - name: Setup .NET for Velopack + - name: Setup Velopack CLI if: ${{ inputs.installer_type == 'velopack' }} - uses: actions/setup-dotnet@v4 + uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack with: - dotnet-version: '9.0.x' - - - name: Install Velopack CLI - if: ${{ inputs.installer_type == 'velopack' }} - shell: bash - run: dotnet tool install -g vpk + setup_exe_url: ${{ inputs.setup_exe_url }} - name: Build and sign Velopack package if: ${{ inputs.installer_type == 'velopack' }} From 18d116385bbce194a19ca6d11137634dd420f6fd Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 17:50:27 -0400 Subject: [PATCH 06/23] Update action.yaml --- sign-pkg-windows/action.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index a52cea6..36e64ba 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -47,9 +47,10 @@ inputs: velopack_installer_base: description: "Base name for installer output files (from build step)" required: false - setup_exe_url: - description: "URL to patched setup.exe for legacy launcher compatibility" + setup_exe_source: + description: "Patched setup.exe source: 'auto' (default), 'none', or a URL" required: false + default: 'auto' runs: using: composite @@ -130,7 +131,7 @@ runs: if: ${{ inputs.installer_type == 'velopack' }} uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack with: - setup_exe_url: ${{ inputs.setup_exe_url }} + setup_exe_source: ${{ inputs.setup_exe_source }} - name: Build and sign Velopack package if: ${{ inputs.installer_type == 'velopack' }} From 03922059e1aec2da8360da167fa6676f24f724ff Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 18:47:11 -0400 Subject: [PATCH 07/23] Update action.yaml --- setup-velopack/action.yaml | 45 +++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/setup-velopack/action.yaml b/setup-velopack/action.yaml index 11b4ed3..abdb7eb 100644 --- a/setup-velopack/action.yaml +++ b/setup-velopack/action.yaml @@ -101,22 +101,55 @@ runs: echo "Downloading 3p-velopack windows64 package..." dl_tmpdir="$(mktemp -d)" - curl -L -f -o "$dl_tmpdir/velopack.tar.bz2" \ + dl_archive="$dl_tmpdir/velopack-pkg" + curl -L -f -o "$dl_archive" \ -H "Accept: application/octet-stream" \ -H "Authorization: Bearer $GH_TOKEN" \ "$ASSET_URL" - # Extract bin/setup.exe from the tarball - # The autobuild package stages it at bin/setup.exe - tar xjf "$dl_tmpdir/velopack.tar.bz2" -C "$dl_tmpdir" "bin/setup.exe" 2>&1 || true + # Extract bin/setup.exe from the archive. + # Autobuild packages are .tar.zst — use Python to handle extraction + # since Windows Git Bash tar may not support zstd natively. + python3 -c " +import tarfile, subprocess, sys, os, shutil + +archive = '$dl_archive' +outdir = '$dl_tmpdir' +target = 'bin/setup.exe' + +# Try native tarfile first (handles gzip, bzip2, xz) +try: + with tarfile.open(archive) as tf: + tf.extract(target, outdir) + print(f'Extracted {target} via tarfile') + sys.exit(0) +except Exception as e: + print(f'tarfile failed ({e}), trying zstd...') + +# Decompress zstd first, then extract +tar_path = archive + '.tar' +try: + # Try system zstd first + subprocess.run(['zstd', '-d', '-f', archive, '-o', tar_path], check=True) +except FileNotFoundError: + # Fall back to pip-installed zstandard + subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', 'zstandard'], check=True) + import importlib + zstandard = importlib.import_module('zstandard') + dctx = zstandard.ZstdDecompressor() + with open(archive, 'rb') as ifh, open(tar_path, 'wb') as ofh: + dctx.copy_stream(ifh, ofh) + +with tarfile.open(tar_path) as tf: + tf.extract(target, outdir) + print(f'Extracted {target} via zstd + tarfile') +" if [[ -f "$dl_tmpdir/bin/setup.exe" ]]; then cp "$dl_tmpdir/bin/setup.exe" "$BUNDLED_SETUP" echo "Replaced setup.exe from 3p-velopack package" else echo "::warning::bin/setup.exe not found in 3p-velopack package, skipping replacement" - echo "Tarball contents:" - tar tjf "$dl_tmpdir/velopack.tar.bz2" | head -20 fi rm -rf "$dl_tmpdir" else From e50e1d8be5acf809de131657534655a5e5567f65 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 18:48:46 -0400 Subject: [PATCH 08/23] Update action.yaml --- setup-velopack/action.yaml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/setup-velopack/action.yaml b/setup-velopack/action.yaml index abdb7eb..32221d1 100644 --- a/setup-velopack/action.yaml +++ b/setup-velopack/action.yaml @@ -110,40 +110,39 @@ runs: # Extract bin/setup.exe from the archive. # Autobuild packages are .tar.zst — use Python to handle extraction # since Windows Git Bash tar may not support zstd natively. - python3 -c " -import tarfile, subprocess, sys, os, shutil + cat > "$dl_tmpdir/extract.py" << 'PYEOF' +import tarfile, subprocess, sys, os -archive = '$dl_archive' -outdir = '$dl_tmpdir' -target = 'bin/setup.exe' +archive = sys.argv[1] +outdir = sys.argv[2] +target = "bin/setup.exe" # Try native tarfile first (handles gzip, bzip2, xz) try: with tarfile.open(archive) as tf: tf.extract(target, outdir) - print(f'Extracted {target} via tarfile') + print("Extracted %s via tarfile" % target) sys.exit(0) except Exception as e: - print(f'tarfile failed ({e}), trying zstd...') + print("tarfile failed (%s), trying zstd..." % e) # Decompress zstd first, then extract -tar_path = archive + '.tar' +tar_path = archive + ".tar" try: - # Try system zstd first - subprocess.run(['zstd', '-d', '-f', archive, '-o', tar_path], check=True) + subprocess.run(["zstd", "-d", "-f", archive, "-o", tar_path], check=True) except FileNotFoundError: - # Fall back to pip-installed zstandard - subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', 'zstandard'], check=True) + subprocess.run([sys.executable, "-m", "pip", "install", "-q", "zstandard"], check=True) import importlib - zstandard = importlib.import_module('zstandard') + zstandard = importlib.import_module("zstandard") dctx = zstandard.ZstdDecompressor() - with open(archive, 'rb') as ifh, open(tar_path, 'wb') as ofh: + with open(archive, "rb") as ifh, open(tar_path, "wb") as ofh: dctx.copy_stream(ifh, ofh) with tarfile.open(tar_path) as tf: tf.extract(target, outdir) - print(f'Extracted {target} via zstd + tarfile') -" + print("Extracted %s via zstd + tarfile" % target) +PYEOF + python3 "$dl_tmpdir/extract.py" "$dl_archive" "$dl_tmpdir" if [[ -f "$dl_tmpdir/bin/setup.exe" ]]; then cp "$dl_tmpdir/bin/setup.exe" "$BUNDLED_SETUP" From a3e46cf6f079c58ccf3741bec77a9b87861e630b Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 18:58:59 -0400 Subject: [PATCH 09/23] Update action.yaml --- setup-velopack/action.yaml | 45 +++++++++----------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/setup-velopack/action.yaml b/setup-velopack/action.yaml index 32221d1..f4e799a 100644 --- a/setup-velopack/action.yaml +++ b/setup-velopack/action.yaml @@ -108,41 +108,16 @@ runs: "$ASSET_URL" # Extract bin/setup.exe from the archive. - # Autobuild packages are .tar.zst — use Python to handle extraction - # since Windows Git Bash tar may not support zstd natively. - cat > "$dl_tmpdir/extract.py" << 'PYEOF' -import tarfile, subprocess, sys, os - -archive = sys.argv[1] -outdir = sys.argv[2] -target = "bin/setup.exe" - -# Try native tarfile first (handles gzip, bzip2, xz) -try: - with tarfile.open(archive) as tf: - tf.extract(target, outdir) - print("Extracted %s via tarfile" % target) - sys.exit(0) -except Exception as e: - print("tarfile failed (%s), trying zstd..." % e) - -# Decompress zstd first, then extract -tar_path = archive + ".tar" -try: - subprocess.run(["zstd", "-d", "-f", archive, "-o", tar_path], check=True) -except FileNotFoundError: - subprocess.run([sys.executable, "-m", "pip", "install", "-q", "zstandard"], check=True) - import importlib - zstandard = importlib.import_module("zstandard") - dctx = zstandard.ZstdDecompressor() - with open(archive, "rb") as ifh, open(tar_path, "wb") as ofh: - dctx.copy_stream(ifh, ofh) - -with tarfile.open(tar_path) as tf: - tf.extract(target, outdir) - print("Extracted %s via zstd + tarfile" % target) -PYEOF - python3 "$dl_tmpdir/extract.py" "$dl_archive" "$dl_tmpdir" + # Autobuild packages are .tar.zst — vpk ships zstd.exe in its vendor dir. + ZSTD="$(find "$VPK_STORE" -name 'zstd.exe' -type f | head -n 1)" + if [[ -n "$ZSTD" ]]; then + echo "Using zstd from vpk: $ZSTD" + "$ZSTD" -d -f "$dl_archive" -o "$dl_tmpdir/velopack.tar" + tar xf "$dl_tmpdir/velopack.tar" -C "$dl_tmpdir" "bin/setup.exe" 2>&1 || true + else + echo "No zstd found, trying tar directly..." + tar xf "$dl_archive" -C "$dl_tmpdir" "bin/setup.exe" 2>&1 || true + fi if [[ -f "$dl_tmpdir/bin/setup.exe" ]]; then cp "$dl_tmpdir/bin/setup.exe" "$BUNDLED_SETUP" From fec47497e0034e032054ee1d96c48134e107e205 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 20 Mar 2026 19:36:03 -0400 Subject: [PATCH 10/23] Update action.yaml --- sign-pkg-mac/action.yaml | 72 ++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 9c8246a..68faab3 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -73,13 +73,53 @@ runs: mkdir -p ".app" tar xJf .tarball/* -C ".app" - # Velopack packaging — run BEFORE signing so that UpdateMac and sq.version - # are injected into the app bundle. The signed DMG will then contain a - # Velopack-ready app bundle that can self-update from day one. + # Velopack packaging — vpk pack handles signing, notarization, and + # injection of UpdateMac + sq.version into the app bundle. - name: Setup Velopack CLI if: ${{ inputs.velopack_pack_id }} uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack + - name: Set up signing keychain + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + cert_base64: "${{ inputs.cert_base64 }}" + cert_pass: "${{ inputs.cert_pass }}" + run: | + set -x -e + # Decode the signing certificate + base64 --decode > certificate.p12 <<< "$cert_base64" + + # Create a temporary keychain for headless CI signing + set +x + keychain_pass="$(dd bs=8 count=1 if=/dev/urandom 2>/dev/null | base64)" + echo "::add-mask::$keychain_pass" + set -x + sleep 1 + security create-keychain -p "$keychain_pass" viewer.keychain + security default-keychain -s viewer.keychain + security unlock-keychain -p "$keychain_pass" viewer.keychain + security import certificate.p12 -k viewer.keychain -P "$cert_pass" \ + -T /usr/bin/codesign + security set-key-partition-list -S 'apple-tool:,apple:,codesign:' -s \ + -k "$keychain_pass" viewer.keychain + rm certificate.p12 + + - name: Store notarytool credentials + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + note_user: "${{ inputs.note_user }}" + note_pass: "${{ inputs.note_pass }}" + note_team: "${{ inputs.note_team }}" + run: | + set -x -e + xcrun notarytool store-credentials "velopack-notary" \ + --apple-id "$note_user" \ + --password "$note_pass" \ + --team-id "$note_team" \ + --keychain viewer.keychain + - name: Build Velopack package if: ${{ inputs.velopack_pack_id }} shell: bash @@ -89,13 +129,15 @@ runs: PACK_TITLE: ${{ inputs.velopack_pack_title }} MAIN_EXE: ${{ inputs.velopack_main_exe }} BUNDLE_ID: ${{ inputs.velopack_bundle_id }} + CERT_NAME: "${{ inputs.cert_name }}" run: | set -x # The app bundle from the build step app_bundle="$(ls -dt .app/*.app | head -n 1)" - # Run vpk pack — this injects UpdateMac and sq.version into the bundle, - # then produces the nupkg and Portable.zip from it. + # Run vpk pack — signs everything (--deep by default), notarizes, + # injects UpdateMac and sq.version, then produces the nupkg and + # Portable.zip from it. vpk pack \ --packId "$PACK_ID" \ --packVersion "$PACK_VERSION" \ @@ -105,7 +147,10 @@ runs: --bundleId "$BUNDLE_ID" \ --outputDir Releases \ --noInst \ - --verbose + --verbose \ + --signAppIdentity "$CERT_NAME" \ + --notaryProfile "velopack-notary" \ + --keychain "viewer.keychain" - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} @@ -204,21 +249,6 @@ runs: # Set the disk image root's custom icon bit SetFile -a C "$volpath" - - name: Sign and notarize the app - shell: bash - env: - cert_base64: "${{ inputs.cert_base64 }}" - cert_name: "${{ inputs.cert_name }}" - cert_pass: "${{ inputs.cert_pass }}" - note_user: "${{ inputs.note_user }}" - note_pass: "${{ inputs.note_pass }}" - note_team: "${{ inputs.note_team }}" - run: | - # Sign the app; do this in the copy that's in the .dmg so that the - # extended attributes used by the signature are preserved; moving the - # files would leave them behind and invalidate the signatures. - "${{ github.action_path }}/sign.sh" "${{ env.app_path }}" - - name: Unmount the sparseimage # unmount even if the above fails if: ${{ ! cancelled() }} From acff8fdda8a975d57fef214d33e3bcd4579f1f70 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 12:44:46 -0400 Subject: [PATCH 11/23] Update action.yaml --- sign-pkg-mac/action.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 68faab3..5574b57 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -118,7 +118,7 @@ runs: --apple-id "$note_user" \ --password "$note_pass" \ --team-id "$note_team" \ - --keychain viewer.keychain + --keychain "$HOME/Library/Keychains/viewer.keychain-db" - name: Build Velopack package if: ${{ inputs.velopack_pack_id }} @@ -150,7 +150,7 @@ runs: --verbose \ --signAppIdentity "$CERT_NAME" \ --notaryProfile "velopack-notary" \ - --keychain "viewer.keychain" + --keychain "$HOME/Library/Keychains/viewer.keychain-db" - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} From 3730c7df17c95d1c7e34ef59ed5c1045bcf545ab Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 13:02:42 -0400 Subject: [PATCH 12/23] Update action.yaml --- sign-pkg-mac/action.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 5574b57..196008e 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -149,6 +149,7 @@ runs: --noInst \ --verbose \ --signAppIdentity "$CERT_NAME" \ + --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ --notaryProfile "velopack-notary" \ --keychain "$HOME/Library/Keychains/viewer.keychain-db" From 703e65a55cfe0cce109eb9dc8f7e997f4b11fb22 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 13:11:19 -0400 Subject: [PATCH 13/23] Update action.yaml --- sign-pkg-mac/action.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 196008e..2a0e314 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -153,6 +153,27 @@ runs: --notaryProfile "velopack-notary" \ --keychain "$HOME/Library/Keychains/viewer.keychain-db" + - name: Fetch notarization log on failure + if: ${{ failure() && inputs.velopack_pack_id }} + shell: bash + run: | + set -x + # Find the notarization job ID from the most recent submission + # notarytool history lists recent submissions; grab the first UUID + job_id=$(xcrun notarytool history \ + --keychain-profile "velopack-notary" \ + --keychain "$HOME/Library/Keychains/viewer.keychain-db" 2>&1 \ + | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \ + | head -n 1) + if [[ -n "$job_id" ]]; then + echo "Fetching notarization log for job $job_id" + xcrun notarytool log "$job_id" \ + --keychain-profile "velopack-notary" \ + --keychain "$HOME/Library/Keychains/viewer.keychain-db" + else + echo "::warning::Could not find notarization job ID" + fi + - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} shell: bash From ddac4241f0480a5afd88c7901f19b52a014061bd Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 13:18:37 -0400 Subject: [PATCH 14/23] Update action.yaml --- sign-pkg-mac/action.yaml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 2a0e314..de48030 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -138,6 +138,7 @@ runs: # Run vpk pack — signs everything (--deep by default), notarizes, # injects UpdateMac and sq.version, then produces the nupkg and # Portable.zip from it. + vpk_rc=0 vpk pack \ --packId "$PACK_ID" \ --packVersion "$PACK_VERSION" \ @@ -151,27 +152,26 @@ runs: --signAppIdentity "$CERT_NAME" \ --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ --notaryProfile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" + --keychain "$HOME/Library/Keychains/viewer.keychain-db" \ + || vpk_rc=$? - - name: Fetch notarization log on failure - if: ${{ failure() && inputs.velopack_pack_id }} - shell: bash - run: | - set -x - # Find the notarization job ID from the most recent submission - # notarytool history lists recent submissions; grab the first UUID - job_id=$(xcrun notarytool history \ - --keychain-profile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" 2>&1 \ - | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \ - | head -n 1) - if [[ -n "$job_id" ]]; then - echo "Fetching notarization log for job $job_id" - xcrun notarytool log "$job_id" \ + if [[ $vpk_rc -ne 0 ]]; then + echo "::error::vpk pack failed with exit code $vpk_rc" + echo "Fetching notarization log from Apple..." + job_id=$(xcrun notarytool history \ --keychain-profile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" - else - echo "::warning::Could not find notarization job ID" + --keychain "$HOME/Library/Keychains/viewer.keychain-db" 2>&1 \ + | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \ + | head -n 1) + if [[ -n "$job_id" ]]; then + echo "Fetching notarization log for job $job_id" + xcrun notarytool log "$job_id" \ + --keychain-profile "velopack-notary" \ + --keychain "$HOME/Library/Keychains/viewer.keychain-db" || true + else + echo "::warning::Could not find notarization job ID" + fi + exit $vpk_rc fi - name: Replace app bundle with Velopack-ready version From ea7bdc3e5765ca733fd6a5b57ba8983d6a50f528 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 13:29:13 -0400 Subject: [PATCH 15/23] Update action.yaml --- sign-pkg-mac/action.yaml | 71 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index de48030..d95e8ea 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -120,6 +120,70 @@ runs: --team-id "$note_team" \ --keychain "$HOME/Library/Keychains/viewer.keychain-db" + - name: Pre-sign nested binaries + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + cert_name: "${{ inputs.cert_name }}" + run: | + set -x -e + app_bundle="$(ls -dt .app/*.app | head -n 1)" + resources="$app_bundle/Contents/Resources" + entitlements="${{ github.action_path }}/installer/slplugin.entitlements" + keychain="viewer.keychain" + + # codesign --deep doesn't recurse into SLPlugin.app or sign + # loose dylibs in Resources/. We must sign these inside-out + # before vpk pack so that notarization passes. + + # 1) Plain-sign all dylibs (no entitlements needed) + find "$resources" -name '*.dylib' -print0 | while IFS= read -r -d '' dylib; do + /usr/bin/codesign --force --timestamp --keychain "$keychain" \ + --sign "$cert_name" "$dylib" + done + + # Also sign dylibs inside CEF framework Libraries/ + find "$app_bundle/Contents/Frameworks" -name '*.dylib' -print0 2>/dev/null | while IFS= read -r -d '' dylib; do + /usr/bin/codesign --force --timestamp --keychain "$keychain" \ + --sign "$cert_name" "$dylib" + done + + # 2) Sign nested app bundles and executables with entitlements + hardened runtime + # DullahanHelper apps (inside SLPlugin.app/Contents/Frameworks/) + find "$resources" -path '*/Frameworks/DullahanHelper*.app' -print0 | while IFS= read -r -d '' helper; do + /usr/bin/codesign --force --deep --timestamp --options runtime \ + --entitlements "$entitlements" --keychain "$keychain" \ + --sign "$cert_name" "$helper" + done + + # CEF framework itself + cef_fw="$resources/SLPlugin.app/Contents/Frameworks/Chromium Embedded Framework.framework" + if [[ -d "$cef_fw" ]]; then + /usr/bin/codesign --force --timestamp --keychain "$keychain" \ + --sign "$cert_name" "$cef_fw" + fi + + # SLPlugin executable + if [[ -f "$resources/SLPlugin.app/Contents/MacOS/SLPlugin" ]]; then + /usr/bin/codesign --force --deep --timestamp --options runtime \ + --entitlements "$entitlements" --keychain "$keychain" \ + --sign "$cert_name" "$resources/SLPlugin.app" + fi + + # SLVoice + if [[ -f "$resources/SLVoice" ]]; then + /usr/bin/codesign --force --timestamp --options runtime \ + --entitlements "$entitlements" --keychain "$keychain" \ + --sign "$cert_name" "$resources/SLVoice" + fi + + # SLVersionChecker (updater) + if [[ -f "$resources/updater/SLVersionChecker" ]]; then + /usr/bin/codesign --force --timestamp --options runtime \ + --entitlements "$entitlements" --keychain "$keychain" \ + --sign "$cert_name" "$resources/updater/SLVersionChecker" + fi + - name: Build Velopack package if: ${{ inputs.velopack_pack_id }} shell: bash @@ -135,9 +199,9 @@ runs: # The app bundle from the build step app_bundle="$(ls -dt .app/*.app | head -n 1)" - # Run vpk pack — signs everything (--deep by default), notarizes, - # injects UpdateMac and sq.version, then produces the nupkg and - # Portable.zip from it. + # Run vpk pack with --signDisableDeep since we pre-signed + # all nested binaries. vpk will sign the top-level bundle + # and handle notarization. vpk_rc=0 vpk pack \ --packId "$PACK_ID" \ @@ -151,6 +215,7 @@ runs: --verbose \ --signAppIdentity "$CERT_NAME" \ --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ + --signDisableDeep \ --notaryProfile "velopack-notary" \ --keychain "$HOME/Library/Keychains/viewer.keychain-db" \ || vpk_rc=$? From edbf6101aed97d40b8f85318c4bb9e9e29211801 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 24 Mar 2026 13:53:11 -0400 Subject: [PATCH 16/23] Update action.yaml --- sign-pkg-mac/action.yaml | 173 ++++++++------------------------------- 1 file changed, 35 insertions(+), 138 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index d95e8ea..3800747 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -73,117 +73,13 @@ runs: mkdir -p ".app" tar xJf .tarball/* -C ".app" - # Velopack packaging — vpk pack handles signing, notarization, and - # injection of UpdateMac + sq.version into the app bundle. + # Velopack packaging — run BEFORE signing so that UpdateMac and sq.version + # are injected into the app bundle. sign.sh then signs everything including + # the Velopack updater binaries. - name: Setup Velopack CLI if: ${{ inputs.velopack_pack_id }} uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack - - name: Set up signing keychain - if: ${{ inputs.velopack_pack_id }} - shell: bash - env: - cert_base64: "${{ inputs.cert_base64 }}" - cert_pass: "${{ inputs.cert_pass }}" - run: | - set -x -e - # Decode the signing certificate - base64 --decode > certificate.p12 <<< "$cert_base64" - - # Create a temporary keychain for headless CI signing - set +x - keychain_pass="$(dd bs=8 count=1 if=/dev/urandom 2>/dev/null | base64)" - echo "::add-mask::$keychain_pass" - set -x - sleep 1 - security create-keychain -p "$keychain_pass" viewer.keychain - security default-keychain -s viewer.keychain - security unlock-keychain -p "$keychain_pass" viewer.keychain - security import certificate.p12 -k viewer.keychain -P "$cert_pass" \ - -T /usr/bin/codesign - security set-key-partition-list -S 'apple-tool:,apple:,codesign:' -s \ - -k "$keychain_pass" viewer.keychain - rm certificate.p12 - - - name: Store notarytool credentials - if: ${{ inputs.velopack_pack_id }} - shell: bash - env: - note_user: "${{ inputs.note_user }}" - note_pass: "${{ inputs.note_pass }}" - note_team: "${{ inputs.note_team }}" - run: | - set -x -e - xcrun notarytool store-credentials "velopack-notary" \ - --apple-id "$note_user" \ - --password "$note_pass" \ - --team-id "$note_team" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" - - - name: Pre-sign nested binaries - if: ${{ inputs.velopack_pack_id }} - shell: bash - env: - cert_name: "${{ inputs.cert_name }}" - run: | - set -x -e - app_bundle="$(ls -dt .app/*.app | head -n 1)" - resources="$app_bundle/Contents/Resources" - entitlements="${{ github.action_path }}/installer/slplugin.entitlements" - keychain="viewer.keychain" - - # codesign --deep doesn't recurse into SLPlugin.app or sign - # loose dylibs in Resources/. We must sign these inside-out - # before vpk pack so that notarization passes. - - # 1) Plain-sign all dylibs (no entitlements needed) - find "$resources" -name '*.dylib' -print0 | while IFS= read -r -d '' dylib; do - /usr/bin/codesign --force --timestamp --keychain "$keychain" \ - --sign "$cert_name" "$dylib" - done - - # Also sign dylibs inside CEF framework Libraries/ - find "$app_bundle/Contents/Frameworks" -name '*.dylib' -print0 2>/dev/null | while IFS= read -r -d '' dylib; do - /usr/bin/codesign --force --timestamp --keychain "$keychain" \ - --sign "$cert_name" "$dylib" - done - - # 2) Sign nested app bundles and executables with entitlements + hardened runtime - # DullahanHelper apps (inside SLPlugin.app/Contents/Frameworks/) - find "$resources" -path '*/Frameworks/DullahanHelper*.app' -print0 | while IFS= read -r -d '' helper; do - /usr/bin/codesign --force --deep --timestamp --options runtime \ - --entitlements "$entitlements" --keychain "$keychain" \ - --sign "$cert_name" "$helper" - done - - # CEF framework itself - cef_fw="$resources/SLPlugin.app/Contents/Frameworks/Chromium Embedded Framework.framework" - if [[ -d "$cef_fw" ]]; then - /usr/bin/codesign --force --timestamp --keychain "$keychain" \ - --sign "$cert_name" "$cef_fw" - fi - - # SLPlugin executable - if [[ -f "$resources/SLPlugin.app/Contents/MacOS/SLPlugin" ]]; then - /usr/bin/codesign --force --deep --timestamp --options runtime \ - --entitlements "$entitlements" --keychain "$keychain" \ - --sign "$cert_name" "$resources/SLPlugin.app" - fi - - # SLVoice - if [[ -f "$resources/SLVoice" ]]; then - /usr/bin/codesign --force --timestamp --options runtime \ - --entitlements "$entitlements" --keychain "$keychain" \ - --sign "$cert_name" "$resources/SLVoice" - fi - - # SLVersionChecker (updater) - if [[ -f "$resources/updater/SLVersionChecker" ]]; then - /usr/bin/codesign --force --timestamp --options runtime \ - --entitlements "$entitlements" --keychain "$keychain" \ - --sign "$cert_name" "$resources/updater/SLVersionChecker" - fi - - name: Build Velopack package if: ${{ inputs.velopack_pack_id }} shell: bash @@ -193,16 +89,14 @@ runs: PACK_TITLE: ${{ inputs.velopack_pack_title }} MAIN_EXE: ${{ inputs.velopack_main_exe }} BUNDLE_ID: ${{ inputs.velopack_bundle_id }} - CERT_NAME: "${{ inputs.cert_name }}" run: | set -x # The app bundle from the build step app_bundle="$(ls -dt .app/*.app | head -n 1)" - # Run vpk pack with --signDisableDeep since we pre-signed - # all nested binaries. vpk will sign the top-level bundle - # and handle notarization. - vpk_rc=0 + # Run vpk pack — unsigned. This injects UpdateMac and sq.version + # into the bundle, then produces the nupkg and Portable.zip. + # Signing and notarization are handled by sign.sh afterward. vpk pack \ --packId "$PACK_ID" \ --packVersion "$PACK_VERSION" \ @@ -212,32 +106,7 @@ runs: --bundleId "$BUNDLE_ID" \ --outputDir Releases \ --noInst \ - --verbose \ - --signAppIdentity "$CERT_NAME" \ - --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ - --signDisableDeep \ - --notaryProfile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" \ - || vpk_rc=$? - - if [[ $vpk_rc -ne 0 ]]; then - echo "::error::vpk pack failed with exit code $vpk_rc" - echo "Fetching notarization log from Apple..." - job_id=$(xcrun notarytool history \ - --keychain-profile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" 2>&1 \ - | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \ - | head -n 1) - if [[ -n "$job_id" ]]; then - echo "Fetching notarization log for job $job_id" - xcrun notarytool log "$job_id" \ - --keychain-profile "velopack-notary" \ - --keychain "$HOME/Library/Keychains/viewer.keychain-db" || true - else - echo "::warning::Could not find notarization job ID" - fi - exit $vpk_rc - fi + --verbose - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} @@ -275,6 +144,34 @@ runs: echo "::warning::UpdateMac binary not found in extracted bundle" fi + - name: Sign and notarize the app + shell: bash + env: + cert_base64: "${{ inputs.cert_base64 }}" + cert_name: "${{ inputs.cert_name }}" + cert_pass: "${{ inputs.cert_pass }}" + note_user: "${{ inputs.note_user }}" + note_pass: "${{ inputs.note_pass }}" + note_team: "${{ inputs.note_team }}" + run: | + # Sign the Velopack-ready app bundle (which includes UpdateMac). + app_bundle="$(ls -dt .app/*.app | head -n 1)" + "${{ github.action_path }}/sign.sh" "$app_bundle" + + - name: Repackage signed Portable.zip + if: ${{ inputs.velopack_pack_id }} + shell: bash + run: | + set -x -e + portable_zip="$(ls Releases/*-Portable.zip | head -n 1)" + if [[ -n "$portable_zip" ]]; then + # Replace the unsigned Portable.zip with the signed bundle + rm "$portable_zip" + app_bundle="$(ls -dt .app/*.app | head -n 1)" + ditto -c -k --keepParent "$app_bundle" "$portable_zip" + echo "Repackaged signed bundle into $portable_zip" + fi + - name: Set up the app sparseimage shell: bash run: | From 866cc312394e3207f47af65c09d2708c1b901c87 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 25 Mar 2026 15:59:18 -0400 Subject: [PATCH 17/23] Update action.yaml --- setup-velopack/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup-velopack/action.yaml b/setup-velopack/action.yaml index f4e799a..e4496a9 100644 --- a/setup-velopack/action.yaml +++ b/setup-velopack/action.yaml @@ -33,7 +33,7 @@ runs: - name: Install Velopack CLI shell: bash - run: dotnet tool install -g vpk + run: dotnet tool install -g vpk --prerelease - name: Replace bundled Setup.exe with patched version if: ${{ runner.os == 'Windows' && inputs.setup_exe_source != 'none' }} From 96a132f7a455884956d13effbbe0c41d4bb6f57e Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Thu, 26 Mar 2026 15:19:00 -0400 Subject: [PATCH 18/23] Update action.yaml --- sign-pkg-mac/action.yaml | 79 +++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 3800747..943e86e 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -73,9 +73,27 @@ runs: mkdir -p ".app" tar xJf .tarball/* -C ".app" - # Velopack packaging — run BEFORE signing so that UpdateMac and sq.version - # are injected into the app bundle. sign.sh then signs everything including - # the Velopack updater binaries. + # Sign and notarize the app bundle BEFORE Velopack packaging so that + # the nupkg contains signed binaries. This ensures that Velopack + # updates install a properly signed bundle, preserving macOS TCC + # permissions (e.g., microphone access for SLVoice). + - name: Sign and notarize the app + shell: bash + env: + cert_base64: "${{ inputs.cert_base64 }}" + cert_name: "${{ inputs.cert_name }}" + cert_pass: "${{ inputs.cert_pass }}" + note_user: "${{ inputs.note_user }}" + note_pass: "${{ inputs.note_pass }}" + note_team: "${{ inputs.note_team }}" + run: | + app_bundle="$(ls -dt .app/*.app | head -n 1)" + "${{ github.action_path }}/sign.sh" "$app_bundle" + + # Velopack packaging — runs AFTER signing so that the nupkg and + # Portable.zip contain signed binaries. vpk injects UpdateMac and + # sq.version, signs only its own additions (UpdateMac), and produces + # the nupkg and Portable.zip. - name: Setup Velopack CLI if: ${{ inputs.velopack_pack_id }} uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack @@ -89,14 +107,15 @@ runs: PACK_TITLE: ${{ inputs.velopack_pack_title }} MAIN_EXE: ${{ inputs.velopack_main_exe }} BUNDLE_ID: ${{ inputs.velopack_bundle_id }} + CERT_NAME: "${{ inputs.cert_name }}" run: | set -x - # The app bundle from the build step app_bundle="$(ls -dt .app/*.app | head -n 1)" - # Run vpk pack — unsigned. This injects UpdateMac and sq.version - # into the bundle, then produces the nupkg and Portable.zip. - # Signing and notarization are handled by sign.sh afterward. + # Run vpk pack on the already-signed bundle. + # --signDisableDeep: don't re-sign our pre-signed binaries + # --signAppIdentity: sign only what vpk adds (UpdateMac, top-level bundle) + # --signEntitlements: use our entitlements for UpdateMac vpk pack \ --packId "$PACK_ID" \ --packVersion "$PACK_VERSION" \ @@ -106,30 +125,29 @@ runs: --bundleId "$BUNDLE_ID" \ --outputDir Releases \ --noInst \ - --verbose + --verbose \ + --signAppIdentity "$CERT_NAME" \ + --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ + --signDisableDeep \ + --keychain "viewer.keychain" - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} shell: bash - env: - PACK_ID: ${{ inputs.velopack_pack_id }} - PACK_TITLE: ${{ inputs.velopack_pack_title }} run: | set -x - # The Portable.zip contains the app bundle with UpdateMac and sq.version - # already injected by vpk pack. Extract it and use it for the DMG. + # The Portable.zip contains the signed app bundle with UpdateMac + # and sq.version injected by vpk pack. portable_zip="$(ls Releases/*-Portable.zip | head -n 1)" if [[ -z "$portable_zip" ]]; then echo "::error::No Portable.zip found in Releases/" exit 1 fi - # Remove the original app bundle and extract the Velopack-ready one + # Replace .app/ with the Velopack-ready bundle rm -rf .app/*.app - # ditto preserves macOS metadata, resource forks, and symlinks ditto -xk "$portable_zip" .app/ - # Verify we got an app bundle new_app="$(ls -dt .app/*.app | head -n 1)" if [[ -z "$new_app" ]]; then echo "::error::No .app bundle found after extracting Portable.zip" @@ -137,41 +155,12 @@ runs: fi echo "Using Velopack-ready app bundle: $new_app" - # Verify UpdateMac is present if [[ -f "$new_app/Contents/MacOS/UpdateMac" ]]; then echo "UpdateMac binary present" else echo "::warning::UpdateMac binary not found in extracted bundle" fi - - name: Sign and notarize the app - shell: bash - env: - cert_base64: "${{ inputs.cert_base64 }}" - cert_name: "${{ inputs.cert_name }}" - cert_pass: "${{ inputs.cert_pass }}" - note_user: "${{ inputs.note_user }}" - note_pass: "${{ inputs.note_pass }}" - note_team: "${{ inputs.note_team }}" - run: | - # Sign the Velopack-ready app bundle (which includes UpdateMac). - app_bundle="$(ls -dt .app/*.app | head -n 1)" - "${{ github.action_path }}/sign.sh" "$app_bundle" - - - name: Repackage signed Portable.zip - if: ${{ inputs.velopack_pack_id }} - shell: bash - run: | - set -x -e - portable_zip="$(ls Releases/*-Portable.zip | head -n 1)" - if [[ -n "$portable_zip" ]]; then - # Replace the unsigned Portable.zip with the signed bundle - rm "$portable_zip" - app_bundle="$(ls -dt .app/*.app | head -n 1)" - ditto -c -k --keepParent "$app_bundle" "$portable_zip" - echo "Repackaged signed bundle into $portable_zip" - fi - - name: Set up the app sparseimage shell: bash run: | From b914644079bdbbb51a003e54dfc92a123f043673 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Thu, 26 Mar 2026 17:22:03 -0400 Subject: [PATCH 19/23] Update action.yaml --- sign-pkg-mac/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 943e86e..aa05872 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -129,7 +129,7 @@ runs: --signAppIdentity "$CERT_NAME" \ --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ --signDisableDeep \ - --keychain "viewer.keychain" + --keychain "$HOME/Library/Keychains/viewer.keychain-db" - name: Replace app bundle with Velopack-ready version if: ${{ inputs.velopack_pack_id }} From 22a17b6786f4f1084d4dfddf325d8b39972ef36b Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 27 Mar 2026 14:26:54 -0400 Subject: [PATCH 20/23] Update action.yaml --- sign-pkg-mac/action.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index aa05872..1e28d11 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -161,6 +161,37 @@ runs: echo "::warning::UpdateMac binary not found in extracted bundle" fi + - name: Re-sign and staple the Velopack-ready bundle + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + cert_name: "${{ inputs.cert_name }}" + note_user: "${{ inputs.note_user }}" + note_pass: "${{ inputs.note_pass }}" + note_team: "${{ inputs.note_team }}" + run: | + set -x -e + app_bundle="$(ls -dt .app/*.app | head -n 1)" + entitlements="${{ github.action_path }}/installer/slplugin.entitlements" + + # vpk added UpdateMac and sq.version which invalidates the top-level + # bundle signature. Inner binaries are still signed from sign.sh. + # Re-sign the top-level bundle to seal the new contents. + /usr/bin/codesign --force --deep --timestamp --options runtime \ + --entitlements "$entitlements" --keychain viewer.keychain \ + --sign "$cert_name" "$app_bundle" + + # Verify the signature is valid + codesign -vvvv "$app_bundle" + spctl -a -texec -vvvv "$app_bundle" + + # Re-staple the notarization ticket (xattrs are lost during vpk's + # copy and zip round-trip) + xcrun stapler staple "$app_bundle" + + # Validate staple + xcrun stapler validate "$app_bundle" + - name: Set up the app sparseimage shell: bash run: | From 7249f5bf1884d1dca5f9ad2c1140a856bc5d1c46 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 27 Mar 2026 15:56:32 -0400 Subject: [PATCH 21/23] Split signing into two steps. --- sign-pkg-mac/action.yaml | 55 ++++++++++++++++++++++++++++++---------- sign-pkg-mac/sign.sh | 7 ++++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index 1e28d11..f4d2ac9 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -73,11 +73,13 @@ runs: mkdir -p ".app" tar xJf .tarball/* -C ".app" - # Sign and notarize the app bundle BEFORE Velopack packaging so that - # the nupkg contains signed binaries. This ensures that Velopack - # updates install a properly signed bundle, preserving macOS TCC - # permissions (e.g., microphone access for SLVoice). - - name: Sign and notarize the app + # Sign the app bundle BEFORE Velopack packaging so that the nupkg + # contains signed binaries. This ensures that Velopack updates install + # a properly signed bundle, preserving macOS TCC permissions (e.g., + # microphone access for SLVoice). Notarization is deferred until after + # vpk adds UpdateMac, since re-signing the bundle invalidates the + # notarization ticket. + - name: Sign the app (defer notarization) shell: bash env: cert_base64: "${{ inputs.cert_base64 }}" @@ -86,6 +88,7 @@ runs: note_user: "${{ inputs.note_user }}" note_pass: "${{ inputs.note_pass }}" note_team: "${{ inputs.note_team }}" + SKIP_NOTARIZE: "true" run: | app_bundle="$(ls -dt .app/*.app | head -n 1)" "${{ github.action_path }}/sign.sh" "$app_bundle" @@ -161,7 +164,7 @@ runs: echo "::warning::UpdateMac binary not found in extracted bundle" fi - - name: Re-sign and staple the Velopack-ready bundle + - name: Re-sign, notarize, and staple the Velopack-ready bundle if: ${{ inputs.velopack_pack_id }} shell: bash env: @@ -175,22 +178,46 @@ runs: entitlements="${{ github.action_path }}/installer/slplugin.entitlements" # vpk added UpdateMac and sq.version which invalidates the top-level - # bundle signature. Inner binaries are still signed from sign.sh. - # Re-sign the top-level bundle to seal the new contents. + # bundle signature and notarization ticket. Inner binaries are still + # signed from sign.sh. Re-sign the top-level bundle to seal the new + # contents, then notarize and staple the final result. /usr/bin/codesign --force --deep --timestamp --options runtime \ --entitlements "$entitlements" --keychain viewer.keychain \ --sign "$cert_name" "$app_bundle" - # Verify the signature is valid + # Verify signature codesign -vvvv "$app_bundle" - spctl -a -texec -vvvv "$app_bundle" - # Re-staple the notarization ticket (xattrs are lost during vpk's - # copy and zip round-trip) - xcrun stapler staple "$app_bundle" + # Notarize + echo "Creating notarization archive" + app_base="$(basename "$app_bundle")" + zip_file="$RUNNER_TEMP/${app_base/.app/.zip}" + ditto -c -k --keepParent "$app_bundle" "$zip_file" + + credentials=(--apple-id "$note_user" --password "$note_pass" --team-id "$note_team") + + echo "Submitting for notarization" + set +e + output="$(xcrun notarytool submit "$zip_file" --wait \ + "${credentials[@]}" 2>&1 | \ + tee /dev/stderr ; \ + exit "${PIPESTATUS[0]}")" + rc=$? + set +x + [[ "$output" =~ 'id: '([^[:space:]]+) ]] + match=$? + set -x + if [[ $match -eq 0 ]]; then + xcrun notarytool log "${BASH_REMATCH[1]}" "${credentials[@]}" + fi + rm -f "$zip_file" + [[ $rc -ne 0 ]] && exit $rc + set -e - # Validate staple + # Staple and validate + xcrun stapler staple "$app_bundle" xcrun stapler validate "$app_bundle" + spctl -a -texec -vvvv "$app_bundle" - name: Set up the app sparseimage shell: bash diff --git a/sign-pkg-mac/sign.sh b/sign-pkg-mac/sign.sh index 463ad6d..f2412ae 100755 --- a/sign-pkg-mac/sign.sh +++ b/sign-pkg-mac/sign.sh @@ -109,7 +109,12 @@ do --sign "$cert_name" "$signee" done -spctl -a -texec -vvvv "$app_path" +spctl -a -texec -vvvv "$app_path" || true + +if [[ "${SKIP_NOTARIZE:-}" == "true" ]]; then + echo "SKIP_NOTARIZE is set, skipping notarization and stapling" + exit 0 +fi # **************************************************************************** # notarize the app From acb34c8f79367f8e2a91a0c9faf069e94370a1b5 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 27 Mar 2026 16:17:45 -0400 Subject: [PATCH 22/23] Update action.yaml --- sign-pkg-mac/action.yaml | 73 +++++++++++++--------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/sign-pkg-mac/action.yaml b/sign-pkg-mac/action.yaml index f4d2ac9..ef33426 100644 --- a/sign-pkg-mac/action.yaml +++ b/sign-pkg-mac/action.yaml @@ -93,10 +93,25 @@ runs: app_bundle="$(ls -dt .app/*.app | head -n 1)" "${{ github.action_path }}/sign.sh" "$app_bundle" - # Velopack packaging — runs AFTER signing so that the nupkg and - # Portable.zip contain signed binaries. vpk injects UpdateMac and - # sq.version, signs only its own additions (UpdateMac), and produces - # the nupkg and Portable.zip. + - name: Store notarytool credentials + if: ${{ inputs.velopack_pack_id }} + shell: bash + env: + note_user: "${{ inputs.note_user }}" + note_pass: "${{ inputs.note_pass }}" + note_team: "${{ inputs.note_team }}" + run: | + set -x -e + xcrun notarytool store-credentials "velopack-notary" \ + --apple-id "$note_user" \ + --password "$note_pass" \ + --team-id "$note_team" \ + --keychain "$HOME/Library/Keychains/viewer.keychain-db" + + # Velopack packaging — runs AFTER signing. vpk injects UpdateMac and + # sq.version, signs its own additions, notarizes the entire bundle + # (including our pre-signed binaries), and produces the nupkg and + # Portable.zip. - name: Setup Velopack CLI if: ${{ inputs.velopack_pack_id }} uses: secondlife/viewer-build-util/setup-velopack@geenz/velopack @@ -119,6 +134,7 @@ runs: # --signDisableDeep: don't re-sign our pre-signed binaries # --signAppIdentity: sign only what vpk adds (UpdateMac, top-level bundle) # --signEntitlements: use our entitlements for UpdateMac + # --notaryProfile: notarize the final bundle (covers all binaries) vpk pack \ --packId "$PACK_ID" \ --packVersion "$PACK_VERSION" \ @@ -132,6 +148,7 @@ runs: --signAppIdentity "$CERT_NAME" \ --signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \ --signDisableDeep \ + --notaryProfile "velopack-notary" \ --keychain "$HOME/Library/Keychains/viewer.keychain-db" - name: Replace app bundle with Velopack-ready version @@ -164,59 +181,17 @@ runs: echo "::warning::UpdateMac binary not found in extracted bundle" fi - - name: Re-sign, notarize, and staple the Velopack-ready bundle + - name: Staple and validate the Velopack-ready bundle if: ${{ inputs.velopack_pack_id }} shell: bash - env: - cert_name: "${{ inputs.cert_name }}" - note_user: "${{ inputs.note_user }}" - note_pass: "${{ inputs.note_pass }}" - note_team: "${{ inputs.note_team }}" run: | set -x -e app_bundle="$(ls -dt .app/*.app | head -n 1)" - entitlements="${{ github.action_path }}/installer/slplugin.entitlements" - - # vpk added UpdateMac and sq.version which invalidates the top-level - # bundle signature and notarization ticket. Inner binaries are still - # signed from sign.sh. Re-sign the top-level bundle to seal the new - # contents, then notarize and staple the final result. - /usr/bin/codesign --force --deep --timestamp --options runtime \ - --entitlements "$entitlements" --keychain viewer.keychain \ - --sign "$cert_name" "$app_bundle" - - # Verify signature - codesign -vvvv "$app_bundle" - # Notarize - echo "Creating notarization archive" - app_base="$(basename "$app_bundle")" - zip_file="$RUNNER_TEMP/${app_base/.app/.zip}" - ditto -c -k --keepParent "$app_bundle" "$zip_file" - - credentials=(--apple-id "$note_user" --password "$note_pass" --team-id "$note_team") - - echo "Submitting for notarization" - set +e - output="$(xcrun notarytool submit "$zip_file" --wait \ - "${credentials[@]}" 2>&1 | \ - tee /dev/stderr ; \ - exit "${PIPESTATUS[0]}")" - rc=$? - set +x - [[ "$output" =~ 'id: '([^[:space:]]+) ]] - match=$? - set -x - if [[ $match -eq 0 ]]; then - xcrun notarytool log "${BASH_REMATCH[1]}" "${credentials[@]}" - fi - rm -f "$zip_file" - [[ $rc -ne 0 ]] && exit $rc - set -e - - # Staple and validate + # vpk already notarized the bundle. Staple the ticket and validate. xcrun stapler staple "$app_bundle" xcrun stapler validate "$app_bundle" + codesign -vvvv "$app_bundle" spctl -a -texec -vvvv "$app_bundle" - name: Set up the app sparseimage From 5c4dabe9a7c949430c3bdce9604b31d18549ae33 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 6 Apr 2026 15:22:26 +0300 Subject: [PATCH 23/23] Velopack icon workaround --- sign-pkg-windows/action.yaml | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sign-pkg-windows/action.yaml b/sign-pkg-windows/action.yaml index 36e64ba..099aa06 100644 --- a/sign-pkg-windows/action.yaml +++ b/sign-pkg-windows/action.yaml @@ -133,6 +133,54 @@ runs: with: setup_exe_source: ${{ inputs.setup_exe_source }} + - name: Stamp icon into Velopack setup template + if: ${{ inputs.installer_type == 'velopack' && inputs.velopack_icon != '' }} + shell: bash + env: + ICON: ${{ inputs.velopack_icon }} + run: | + set -x + if [[ ! -f ".app/$ICON" ]]; then + echo "Icon not found at .app/$ICON, skipping template icon stamp" + exit 0 + fi + + # Find the vpk vendor setup.exe template + VPK_STORE="" + for candidate in \ + "$HOME/.dotnet/tools/.store/vpk" \ + "$USERPROFILE/.dotnet/tools/.store/vpk" \ + "$(cygpath "$USERPROFILE" 2>/dev/null)/.dotnet/tools/.store/vpk" + do + if [[ -d "$candidate" ]]; then + VPK_STORE="$candidate" + break + fi + done + + if [[ -z "$VPK_STORE" ]]; then + echo "::warning::vpk tool store not found, skipping template icon stamp" + exit 0 + fi + + TEMPLATE_SETUP="$(find "$VPK_STORE" -iname 'setup.exe' -type f | head -n 1)" + if [[ -z "$TEMPLATE_SETUP" ]]; then + echo "::warning::setup.exe template not found in vpk store, skipping icon stamp" + exit 0 + fi + echo "Found setup.exe template at: $TEMPLATE_SETUP" + + # Download rcedit and stamp the icon into the template PE resources. + # vpk's --icon flag bundles the .ico inside the archive but does not + # update the PE RT_ICON resources when --signTemplate is used, so + # Windows Explorer shows a generic icon instead of the app icon. + # By stamping the template before vpk pack, the final Setup.exe + # inherits the correct icon and vpk signs the result normally. + rcedit_url="https://github.com/electron/rcedit/releases/download/v2.0.0/rcedit-x64.exe" + curl -L -f -o rcedit.exe "$rcedit_url" + ./rcedit.exe "$TEMPLATE_SETUP" --set-icon ".app/$ICON" + echo "Stamped icon into setup.exe template" + - name: Build and sign Velopack package if: ${{ inputs.installer_type == 'velopack' }} shell: bash