Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions setup-velopack/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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 --prerelease

- 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)"
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 archive.
# 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"
echo "Replaced setup.exe from 3p-velopack package"
else
echo "::warning::bin/setup.exe not found in 3p-velopack package, skipping replacement"
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"
157 changes: 142 additions & 15 deletions sign-pkg-mac/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@
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
Expand All @@ -58,6 +73,127 @@
mkdir -p ".app"
tar xJf .tarball/* -C ".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 }}"
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 }}"
SKIP_NOTARIZE: "true"
run: |
app_bundle="$(ls -dt .app/*.app | head -n 1)"
"${{ github.action_path }}/sign.sh" "$app_bundle"

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.
Comment thread
Geenz marked this conversation as resolved.
Dismissed

- 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

- 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 }}
CERT_NAME: "${{ inputs.cert_name }}"
run: |
set -x
app_bundle="$(ls -dt .app/*.app | head -n 1)"

# 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
# --notaryProfile: notarize the final bundle (covers all binaries)
vpk pack \
--packId "$PACK_ID" \
--packVersion "$PACK_VERSION" \
--packDir "$app_bundle" \
--packTitle "$PACK_TITLE" \
--mainExe "$MAIN_EXE" \
--bundleId "$BUNDLE_ID" \
--outputDir Releases \
--noInst \
--verbose \
--signAppIdentity "$CERT_NAME" \
--signEntitlements "${{ github.action_path }}/installer/slplugin.entitlements" \

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.
Comment thread
Geenz marked this conversation as resolved.
Dismissed
--signDisableDeep \
--notaryProfile "velopack-notary" \
--keychain "$HOME/Library/Keychains/viewer.keychain-db"

- name: Replace app bundle with Velopack-ready version
if: ${{ inputs.velopack_pack_id }}
shell: bash
run: |
set -x
# 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

# Replace .app/ with the Velopack-ready bundle
rm -rf .app/*.app
ditto -xk "$portable_zip" .app/

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"
if [[ -f "$new_app/Contents/MacOS/UpdateMac" ]]; then
echo "UpdateMac binary present"
else
echo "::warning::UpdateMac binary not found in extracted bundle"
fi

- name: Staple and validate the Velopack-ready bundle
if: ${{ inputs.velopack_pack_id }}
shell: bash
run: |
set -x -e
app_bundle="$(ls -dt .app/*.app | head -n 1)"

# 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
shell: bash
run: |
Expand Down Expand Up @@ -119,21 +255,6 @@
# 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() }}
Expand Down Expand Up @@ -177,3 +298,9 @@
name: "macOS-installer"
path: ${{ env.installer }}

- name: Upload Velopack releases
if: ${{ inputs.velopack_pack_id }}
uses: actions/upload-artifact@v4
with:
name: "macOS-releases"
path: Releases/
7 changes: 6 additions & 1 deletion sign-pkg-mac/sign.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading