Skip to content

Sign and Repackage (Release Only) #294

Sign and Repackage (Release Only)

Sign and Repackage (Release Only) #294

# 签名并重新打包工作流(仅用于正式发布版本)
# 流程:
# 1. 编译当前分支的代码(只编译一次)
# 2. 生成未签名的 ZIP 和 Inno Setup staging 目录
# 3. 将所有 EXE/DLL 文件提交到 SignPath 签名
# 4. 用签名文件重新打包 ZIP 便携版
# 5. 用签名文件替换 Inno Setup staging 目录中的文件并重新打包
# 6. 签名最终的 Inno Setup 安装包
# 注意:只在正式发布(非预发布)时运行,确保代码版本和签名文件一致
name: Sign and Repackage (Release Only)
on:
release:
types: [published]
workflow_dispatch:
inputs:
ref:
description: "Branch or tag to checkout (e.g., master, v2025.1116)"
required: true
default: "master"
release-tag:
description: "Release tag for naming the output files"
required: true
# Opt all Node.js 20 actions onto the Node.js 24 runtime to silence the
# `actions/checkout@v4` deprecation warning until upstream actions cut new
# releases.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
jobs:
sign-and-repackage:
name: Sign Files and Repackage
runs-on: windows-latest
# 只在正式发布时运行(不是预发布)
if: ${{ (github.event_name == 'release' && !github.event.release.prerelease) || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref || github.ref }}
submodules: recursive
- name: Setup Dependencies Windows
uses: msys2/setup-msys2@v2
with:
msystem: ucrt64
update: true
install: >-
wget
- name: Update Windows dependencies
env:
gcc_version: '15.1.0-5'
shell: msys2 {0}
run: |
broken_deps=(
"mingw-w64-ucrt-x86_64-gcc"
"mingw-w64-ucrt-x86_64-gcc-libs"
)
tarballs=""
for dep in "${broken_deps[@]}"; do
tarball="${dep}-${gcc_version}-any.pkg.tar.zst"
# download and install working version
wget https://repo.msys2.org/mingw/ucrt64/${tarball}
tarballs="${tarballs} ${tarball}"
done
# install broken dependencies
if [ -n "$tarballs" ]; then
pacman -U --noconfirm ${tarballs}
fi
# install dependencies
dependencies=(
"git"
"mingw-w64-ucrt-x86_64-cmake"
"mingw-w64-ucrt-x86_64-ninja"
"mingw-w64-ucrt-x86_64-cppwinrt"
"mingw-w64-ucrt-x86_64-curl-winssl"
"mingw-w64-ucrt-x86_64-graphviz"
"mingw-w64-ucrt-x86_64-MinHook"
"mingw-w64-ucrt-x86_64-miniupnpc"
"mingw-w64-ucrt-x86_64-nlohmann-json"
"mingw-w64-ucrt-x86_64-nodejs"
# "mingw-w64-ucrt-x86_64-nsis" # Replaced by Inno Setup
"mingw-w64-ucrt-x86_64-onevpl"
"mingw-w64-ucrt-x86_64-openssl"
"mingw-w64-ucrt-x86_64-opus"
"mingw-w64-ucrt-x86_64-toolchain"
)
# Note: mingw-w64-ucrt-x86_64-rust conflicts with fixed gcc-15.1.0-5
# We install Rust via rustup in a separate step
pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}"
- name: Install Rust (for Tauri GUI)
shell: msys2 {0}
run: |
echo "Installing Rust via rustup..."
# Rust installs to Windows user directory
WINDOWS_USER=$(cmd //c "echo %USERNAME%" | tr -d '\r')
CARGO_BIN="/c/Users/${WINDOWS_USER}/.cargo/bin"
export PATH="$CARGO_BIN:$PATH"
# Check if cargo already exists
if command -v cargo &> /dev/null; then
echo "Rust already installed: $(cargo --version)"
else
# Download and install rustup
curl --proto '=https' --tlsv1.2 -sSf https://win.rustup.rs/x86_64 -o /tmp/rustup-init.exe
/tmp/rustup-init.exe -y --default-toolchain stable --profile minimal
# Refresh PATH
sleep 3
export PATH="$CARGO_BIN:$PATH"
# Verify installation
if [ -f "$CARGO_BIN/cargo.exe" ]; then
echo "Rust installed successfully: $(cargo --version)"
else
echo "Warning: Rust installed but cargo not found at $CARGO_BIN"
exit 1
fi
fi
- name: Verify Build Tools
shell: msys2 {0}
run: |
echo "Verifying build tools are installed..."
which cmake || (echo "cmake not found" && exit 1)
which ninja || (echo "ninja not found" && exit 1)
which gcc || (echo "gcc not found" && exit 1)
# verify Rust is in PATH
WINDOWS_USER=$(cmd //c "echo %USERNAME%" | tr -d '\r')
CARGO_BIN="/c/Users/${WINDOWS_USER}/.cargo/bin"
export PATH="$CARGO_BIN:$PATH"
which cargo || (echo "cargo not found" && exit 1)
echo "All build tools verified successfully"
echo " CMake: $(cmake --version | head -1)"
echo " Ninja: $(ninja --version)"
echo " GCC: $(gcc --version | head -1)"
echo " Cargo: $(cargo --version)"
- name: Build Windows
shell: msys2 {0}
env:
BRANCH: master
BUILD_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release-tag }}.杂鱼
COMMIT: ${{ github.sha }}
run: |
# add Rust to PATH for Tauri GUI build
WINDOWS_USER=$(cmd //c "echo %USERNAME%" | tr -d '\r')
CARGO_BIN="/c/Users/${WINDOWS_USER}/.cargo/bin"
export PATH="$CARGO_BIN:$PATH"
mkdir -p build
cmake \
-B build \
-G Ninja \
-S . \
-DBUILD_DOCS=OFF \
-DSUNSHINE_ASSETS_DIR=assets \
-DSUNSHINE_PUBLISHER_NAME='${{ github.repository_owner }}' \
-DSUNSHINE_PUBLISHER_WEBSITE='https://github.com/qiin2333/Sunshine-Foundation' \
-DSUNSHINE_PUBLISHER_ISSUE_URL='https://github.com/qiin2333/Sunshine-Foundation/issues'
ninja -C build
ninja -C build sunshine-control-panel
# Install Inno Setup
- name: Install Inno Setup
shell: pwsh
run: |
$url = "https://jrsoftware.org/download.php/is.exe"
$installer = "$env:TEMP\innosetup.exe"
Invoke-WebRequest -Uri $url -OutFile $installer
Start-Process -FilePath $installer -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' -Wait
echo "C:\Program Files (x86)\Inno Setup 6" >> $env:GITHUB_PATH
# 生成未签名的打包产物
- name: Package unsigned files
shell: msys2 {0}
run: |
cd build
# 生成 ZIP 便携版(包含所有文件)
cpack -G ZIP --verbose
# 生成 Inno Setup staging 目录
echo "Generating Inno Setup staging directory..."
cmake --install . --prefix ./inno_staging
cd ..
# 列出生成的文件
echo "Generated files:"
ls -lh build/cpack_artifacts/
# 解压 Portable ZIP 获取所有需要签名的文件
- name: Extract files for signing
shell: bash
run: |
PORTABLE=$(find build/cpack_artifacts -name "*.zip" | head -n 1)
if [ -n "$PORTABLE" ]; then
echo "Extracting: $PORTABLE"
mkdir -p unsigned-files
7z x "$PORTABLE" -o"unsigned-files" -y -aoa
echo "Extracted files for signing:"
ls -laR unsigned-files/
else
echo "No portable ZIP found"
exit 1
fi
# 上传所有未签名的文件到 SignPath
- name: Upload unsigned files for signing
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: files-for-signing
path: unsigned-files/
# 提交到 SignPath 签名(使用生产策略)
- name: Submit to SignPath for signing
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: Sunshine-Foundation
signing-policy-slug: release-signing
artifact-configuration-slug: windows-portable
github-artifact-id: "${{ steps.upload-unsigned.outputs.artifact-id }}"
output-artifact-directory: signed-files
wait-for-completion: true
wait-for-completion-timeout-in-seconds: 600
service-unavailable-timeout-in-seconds: 600
# 验证签名
- name: Verify all signatures
shell: pwsh
run: |
Write-Host "Verifying signatures..."
$signedFiles = Get-ChildItem -Path "signed-files" -Recurse -File -Include *.exe,*.dll
foreach ($file in $signedFiles) {
Write-Host "`n=== $($file.Name) ==="
$signature = Get-AuthenticodeSignature $file.FullName
Write-Host "Status: $($signature.Status)"
if ($signature.Status -ne "Valid") {
Write-Error "Signature invalid for: $($file.Name)"
exit 1
}
}
Write-Host "`n✓ All signatures are VALID!" -ForegroundColor Green
# 使用签名后的文件重新打包 ZIP 便携版
- name: Repackage signed Portable ZIP
shell: bash
run: |
cd signed-files
# 使用与输入一致的版本号
if [ -n "${{ github.event.inputs.release-tag }}" ]; then
VERSION="${{ github.event.inputs.release-tag }}"
elif [ -n "${{ github.event.release.tag_name }}" ]; then
VERSION="${{ github.event.release.tag_name }}"
else
VERSION="v$(date +%Y.%m%d)"
fi
7z a "../Sunshine-${VERSION}-Windows-Portable-Signed.zip" * -y
cd ..
echo "Created signed portable package:"
ls -lh Sunshine-*-Portable-Signed.zip
# 使用签名文件重新打包 Inno Setup 安装包
- name: Repackage signed Inno Setup installer
shell: msys2 {0}
run: |
echo "Repackaging Inno Setup installer with signed files..."
STAGING_DIR="build/inno_staging"
# 检查 staging 目录是否存在
if [ ! -d "$STAGING_DIR" ]; then
echo "Error: Inno Setup staging directory not found: $STAGING_DIR"
ls -laR build/
exit 1
fi
# 替换为签名后的文件
SIGNED_SRC="signed-files/Sunshine"
echo "Replacing with signed files..."
cp -fv "$SIGNED_SRC/sunshine.exe" "$STAGING_DIR/sunshine.exe"
cp -fv "$SIGNED_SRC/zlib1.dll" "$STAGING_DIR/zlib1.dll"
cp -fv "$SIGNED_SRC/tools/sunshinesvc.exe" "$STAGING_DIR/tools/sunshinesvc.exe"
cp -fv "$SIGNED_SRC/tools/qiin-tabtip.exe" "$STAGING_DIR/tools/qiin-tabtip.exe"
cp -fv "$SIGNED_SRC/tools/device-toggler.exe" "$STAGING_DIR/tools/device-toggler.exe"
cp -fv "$SIGNED_SRC/tools/DevManView.exe" "$STAGING_DIR/tools/DevManView.exe"
cp -fv "$SIGNED_SRC/tools/restart64.exe" "$STAGING_DIR/tools/restart64.exe"
cp -fv "$SIGNED_SRC/tools/SetDpi.exe" "$STAGING_DIR/tools/SetDpi.exe"
cp -fv "$SIGNED_SRC/tools/setreg.exe" "$STAGING_DIR/tools/setreg.exe"
cp -fv "$SIGNED_SRC/tools/audio-info.exe" "$STAGING_DIR/tools/audio-info.exe"
cp -fv "$SIGNED_SRC/tools/dxgi-info.exe" "$STAGING_DIR/tools/dxgi-info.exe"
cp -fv "$SIGNED_SRC/assets/gui/sunshine-gui.exe" "$STAGING_DIR/assets/gui/sunshine-gui.exe"
# 运行 Inno Setup 编译器重新打包
echo "Running ISCC to repackage installer..."
"/c/Program Files (x86)/Inno Setup 6/ISCC.exe" "build/sunshine_installer.iss"
# 使用与 Portable ZIP 一致的版本号
if [ -n "${{ github.event.inputs.release-tag }}" ]; then
VERSION="${{ github.event.inputs.release-tag }}"
elif [ -n "${{ github.event.release.tag_name }}" ]; then
VERSION="${{ github.event.release.tag_name }}"
else
VERSION="v$(date +%Y.%m%d)"
fi
mv -fv "build/cpack_artifacts/Sunshine.exe" "Sunshine-${VERSION}-Windows-Installer-Signed.exe"
echo "Created signed installer:"
ls -lh Sunshine-*-Installer-Signed.exe
# 签名最终的 Inno Setup 安装包
- name: Upload installer for final signing
id: upload-installer
uses: actions/upload-artifact@v4
with:
name: installer-for-final-signing
path: Sunshine-*-Installer-Signed.exe
- name: Sign installer
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: Sunshine-Foundation
signing-policy-slug: release-signing
artifact-configuration-slug: windows-installer
github-artifact-id: "${{ steps.upload-installer.outputs.artifact-id }}"
output-artifact-directory: final-signed
wait-for-completion: true
wait-for-completion-timeout-in-seconds: 600
service-unavailable-timeout-in-seconds: 600
# 验证最终签名
- name: Verify final installer signature
shell: pwsh
run: |
$installer = Get-ChildItem "final-signed" -Filter "*.exe" | Select-Object -First 1
if ($installer) {
Write-Host "Verifying installer signature..."
$signature = Get-AuthenticodeSignature $installer.FullName
Write-Host "Status: $($signature.Status)"
if ($signature.Status -eq "Valid") {
Write-Host "✓ Installer signature is VALID!" -ForegroundColor Green
}
}
# 上传最终的签名文件
- name: Upload final signed packages
uses: actions/upload-artifact@v4
with:
name: sunshine-windows-fully-signed
path: |
Sunshine-*-Portable-Signed.zip
final-signed/*.exe
if-no-files-found: error
# 发布到 GitHub Release
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release-tag }}
name: ${{ github.event_name == 'release' && github.event.release.name || github.event.inputs.release-tag }} (Signed)
files: |
Sunshine-*-Portable-Signed.zip
final-signed/*.exe
draft: true
prerelease: true
allowUpdates: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}