Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 43s
Replaces default NSIS icon with the Wraith ghost logo. Icon included in install directory for Start Menu and Desktop shortcut references. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
429 lines
17 KiB
YAML
429 lines
17 KiB
YAML
# =============================================================================
|
|
# Wraith — Build & Sign Release
|
|
# =============================================================================
|
|
# Builds the Wails v3 desktop app for Windows amd64, cross-compiles FreeRDP3
|
|
# from source via MinGW, signs everything with Azure Key Vault EV cert,
|
|
# then uploads to SeaweedFS.
|
|
#
|
|
# Trigger: push a tag matching v* (e.g. v1.0.0) or run manually.
|
|
#
|
|
# Required secrets:
|
|
# AZURE_TENANT_ID — Azure AD tenant
|
|
# AZURE_CLIENT_ID — Service principal client ID
|
|
# AZURE_CLIENT_SECRET — Service principal secret
|
|
# AZURE_KEY_VAULT_URL — e.g. https://my-vault.vault.azure.net
|
|
# AZURE_CERT_NAME — Certificate/key name in the vault
|
|
# GIT_TOKEN — PAT for cloning private repo
|
|
# =============================================================================
|
|
|
|
name: Build & Sign Wraith
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-and-sign:
|
|
name: Build Windows + Sign
|
|
runs-on: linux
|
|
steps:
|
|
# ---------------------------------------------------------------
|
|
# Checkout
|
|
# ---------------------------------------------------------------
|
|
- name: Checkout code
|
|
run: git clone --depth 1 --branch ${{ github.ref_name }} https://${{ secrets.GIT_TOKEN }}@git.command.vigilcyber.com/vstockwell/wraith.git .
|
|
|
|
# ---------------------------------------------------------------
|
|
# Extract version from tag
|
|
# ---------------------------------------------------------------
|
|
- name: Get version from tag
|
|
id: version
|
|
run: |
|
|
TAG=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
|
echo "version=${TAG}" >> $GITHUB_OUTPUT
|
|
echo "Building version: ${TAG}"
|
|
|
|
# ---------------------------------------------------------------
|
|
# Install toolchain
|
|
# ---------------------------------------------------------------
|
|
- name: Install build dependencies
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq \
|
|
mingw-w64 mingw-w64-tools binutils-mingw-w64 \
|
|
cmake ninja-build nasm meson \
|
|
default-jre-headless \
|
|
python3 curl pkg-config nsis
|
|
|
|
# Node.js 22 — Tailwind CSS v4 and Naive UI require Node >= 20
|
|
NODE_MAJOR=$(node --version 2>/dev/null | sed 's/v\([0-9]*\).*/\1/' || echo "0")
|
|
if [ "$NODE_MAJOR" -lt 20 ]; then
|
|
echo "Node $NODE_MAJOR is too old, installing Node 22..."
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
apt-get install -y -qq nodejs
|
|
fi
|
|
|
|
echo "=== Toolchain versions ==="
|
|
go version
|
|
node --version
|
|
x86_64-w64-mingw32-gcc --version | head -1
|
|
cmake --version | head -1
|
|
|
|
# ===============================================================
|
|
# FreeRDP3 dependencies — cross-compile zlib + OpenSSL for MinGW
|
|
# ===============================================================
|
|
- name: Build FreeRDP3 dependencies (zlib + OpenSSL for MinGW)
|
|
run: |
|
|
INSTALL_PREFIX="/tmp/mingw-deps"
|
|
mkdir -p "$INSTALL_PREFIX"
|
|
export CROSS=x86_64-w64-mingw32
|
|
|
|
# --- zlib ---
|
|
echo "=== Building zlib for MinGW ==="
|
|
ZLIB_VERSION="1.3.1"
|
|
curl -sSL -o /tmp/zlib.tar.gz "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz"
|
|
tar -xzf /tmp/zlib.tar.gz -C /tmp
|
|
cd /tmp/zlib-${ZLIB_VERSION}
|
|
CC=${CROSS}-gcc AR=${CROSS}-ar RANLIB=${CROSS}-ranlib \
|
|
./configure --prefix="$INSTALL_PREFIX" --static
|
|
make -j$(nproc)
|
|
make install
|
|
echo "zlib installed to $INSTALL_PREFIX"
|
|
|
|
# --- OpenSSL ---
|
|
echo "=== Building OpenSSL for MinGW ==="
|
|
OPENSSL_VERSION="3.4.1"
|
|
curl -sSL -o /tmp/openssl.tar.gz "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz"
|
|
tar -xzf /tmp/openssl.tar.gz -C /tmp
|
|
cd /tmp/openssl-${OPENSSL_VERSION}
|
|
./Configure mingw64 no-shared no-tests \
|
|
--cross-compile-prefix=${CROSS}- \
|
|
--prefix="$INSTALL_PREFIX"
|
|
make -j$(nproc)
|
|
make install_sw
|
|
echo "OpenSSL installed to $INSTALL_PREFIX"
|
|
|
|
# ===============================================================
|
|
# FreeRDP3 — Cross-compile from source via MinGW
|
|
# ===============================================================
|
|
- name: Build FreeRDP3 for Windows (MinGW cross-compile)
|
|
run: |
|
|
FREERDP_VERSION="3.10.3"
|
|
INSTALL_PREFIX="/tmp/mingw-deps"
|
|
echo "=== Building FreeRDP ${FREERDP_VERSION} for Windows amd64 via MinGW ==="
|
|
|
|
# Nuke any cached source/build from previous runs
|
|
rm -rf /tmp/FreeRDP-* /tmp/freerdp-install /tmp/freerdp.tar.gz
|
|
|
|
# Download FreeRDP source
|
|
curl -sSL -o /tmp/freerdp.tar.gz "https://github.com/FreeRDP/FreeRDP/archive/refs/tags/${FREERDP_VERSION}.tar.gz"
|
|
tar -xzf /tmp/freerdp.tar.gz -C /tmp
|
|
cd /tmp/FreeRDP-${FREERDP_VERSION}
|
|
|
|
# Create MinGW toolchain file pointing to our built deps
|
|
cat > /tmp/mingw-toolchain.cmake << TCEOF
|
|
set(CMAKE_SYSTEM_NAME Windows)
|
|
set(CMAKE_SYSTEM_PROCESSOR AMD64)
|
|
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
|
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
|
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32;${INSTALL_PREFIX})
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
|
set(CMAKE_PREFIX_PATH ${INSTALL_PREFIX})
|
|
set(OPENSSL_ROOT_DIR ${INSTALL_PREFIX})
|
|
set(ZLIB_ROOT ${INSTALL_PREFIX})
|
|
TCEOF
|
|
|
|
# Force suppress all warnings via environment — FreeRDP cmake overrides CMAKE_C_FLAGS
|
|
export CFLAGS="-w"
|
|
export CXXFLAGS="-w"
|
|
|
|
# Stub out unwind source — uses dlfcn.h which doesn't exist in MinGW
|
|
# Replace with empty implementations so cmake still finds the file
|
|
cat > winpr/libwinpr/utils/unwind/debug.c << 'STUBEOF'
|
|
#include <winpr/wtypes.h>
|
|
void* winpr_unwind_backtrace(void) { return NULL; }
|
|
char** winpr_unwind_backtrace_symbols(void* buffer, size_t* used) { *used = 0; return NULL; }
|
|
void winpr_unwind_backtrace_free(void* buffer) { }
|
|
STUBEOF
|
|
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_TOOLCHAIN_FILE=/tmp/mingw-toolchain.cmake \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DCMAKE_C_FLAGS_INIT="-w" \
|
|
-DCMAKE_INSTALL_PREFIX=/tmp/freerdp-install \
|
|
-DBUILD_SHARED_LIBS=ON \
|
|
-DWITH_CLIENT=OFF \
|
|
-DWITH_CLIENT_SDL=OFF \
|
|
-DWITH_CLIENT_WINDOWS=OFF \
|
|
-DWITH_SERVER=OFF \
|
|
-DWITH_SHADOW=OFF \
|
|
-DWITH_PROXY=OFF \
|
|
-DWITH_SAMPLE=OFF \
|
|
-DWITH_PLATFORM_SERVER=OFF \
|
|
-DWITH_WINPR_TOOLS=OFF \
|
|
-DWITH_FFMPEG=OFF \
|
|
-DWITH_SWSCALE=OFF \
|
|
-DWITH_CAIRO=OFF \
|
|
-DWITH_CUPS=OFF \
|
|
-DWITH_PULSE=OFF \
|
|
-DWITH_ALSA=OFF \
|
|
-DWITH_OSS=OFF \
|
|
-DWITH_WAYLAND=OFF \
|
|
-DWITH_X11=OFF \
|
|
-DCHANNEL_URBDRC=OFF \
|
|
-DWITH_OPENH264=OFF \
|
|
-DWITH_LIBSYSTEMD=OFF \
|
|
-DWITH_UNWIND=OFF \
|
|
-DWITH_PCSC=OFF \
|
|
-DWITH_SMARTCARD=OFF
|
|
|
|
# Build — use -v for verbose output on failure
|
|
cmake --build build --parallel $(nproc) -- -v 2>&1 || {
|
|
echo "=== Build failed — retrying with single thread for clear error output ==="
|
|
cmake --build build -j1 -- -v 2>&1 | tail -50
|
|
exit 1
|
|
}
|
|
cmake --install build
|
|
|
|
echo "=== FreeRDP3 DLLs built ==="
|
|
ls -la /tmp/freerdp-install/bin/*.dll 2>/dev/null || ls -la /tmp/freerdp-install/lib/*.dll 2>/dev/null || echo "Checking build output..."
|
|
find /tmp/freerdp-install -name "*.dll" -type f
|
|
|
|
- name: Stage FreeRDP3 DLLs
|
|
run: |
|
|
mkdir -p dist
|
|
|
|
# Copy all FreeRDP DLLs (MinGW produces lib-prefixed names)
|
|
find /tmp/freerdp-install -name "*.dll" -type f -exec cp {} dist/ \;
|
|
|
|
echo "=== Staged DLLs ==="
|
|
ls -la dist/*.dll 2>/dev/null || echo "No DLLs found — FreeRDP build may have failed"
|
|
|
|
# ===============================================================
|
|
# Build Wraith
|
|
# ===============================================================
|
|
- name: Build frontend
|
|
run: |
|
|
cd frontend
|
|
npm ci
|
|
npm run build
|
|
echo "Frontend build complete:"
|
|
ls -la dist/
|
|
|
|
- name: Build wraith.exe (Windows amd64)
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
echo "=== Cross-compiling wraith.exe for Windows amd64 ==="
|
|
|
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
|
|
go build \
|
|
-ldflags="-s -w -H windowsgui -X main.version=${VERSION}" \
|
|
-o dist/wraith.exe \
|
|
.
|
|
|
|
ls -la dist/wraith.exe
|
|
|
|
# ===============================================================
|
|
# Code signing — jsign + Azure Key Vault (EV cert)
|
|
# ===============================================================
|
|
- name: Install jsign
|
|
run: |
|
|
JSIGN_VERSION="7.0"
|
|
curl -sSL -o /usr/local/bin/jsign.jar \
|
|
"https://github.com/ebourg/jsign/releases/download/${JSIGN_VERSION}/jsign-${JSIGN_VERSION}.jar"
|
|
|
|
- name: Get Azure Key Vault access token
|
|
id: azure-token
|
|
run: |
|
|
TOKEN=$(curl -s -X POST \
|
|
"https://login.microsoftonline.com/${{ secrets.AZURE_TENANT_ID }}/oauth2/v2.0/token" \
|
|
-d "client_id=${{ secrets.AZURE_CLIENT_ID }}" \
|
|
-d "client_secret=${{ secrets.AZURE_CLIENT_SECRET }}" \
|
|
-d "scope=https://vault.azure.net/.default" \
|
|
-d "grant_type=client_credentials" \
|
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
|
|
echo "::add-mask::${TOKEN}"
|
|
echo "token=${TOKEN}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Sign all Windows binaries
|
|
run: |
|
|
echo "=== Signing all .exe and .dll files with EV certificate ==="
|
|
for binary in dist/*.exe dist/*.dll; do
|
|
[ -f "$binary" ] || continue
|
|
echo "Signing: $binary"
|
|
java -jar /usr/local/bin/jsign.jar \
|
|
--storetype AZUREKEYVAULT \
|
|
--keystore "${{ secrets.AZURE_KEY_VAULT_URL }}" \
|
|
--storepass "${{ steps.azure-token.outputs.token }}" \
|
|
--alias "${{ secrets.AZURE_CERT_NAME }}" \
|
|
--tsaurl http://timestamp.digicert.com \
|
|
--tsmode RFC3161 \
|
|
"$binary"
|
|
echo "Signed: $binary"
|
|
done
|
|
|
|
# ===============================================================
|
|
# Version manifest
|
|
# ===============================================================
|
|
- name: Create version.json
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
EXE_SHA=$(sha256sum dist/wraith.exe | awk '{print $1}')
|
|
|
|
# Build DLL manifest
|
|
DLL_ENTRIES=""
|
|
for dll in dist/*.dll; do
|
|
[ -f "$dll" ] || continue
|
|
DLL_NAME=$(basename "$dll")
|
|
DLL_SHA=$(sha256sum "$dll" | awk '{print $1}')
|
|
DLL_ENTRIES="${DLL_ENTRIES} \"${DLL_NAME}\": \"${DLL_SHA}\",
|
|
"
|
|
done
|
|
|
|
cat > dist/version.json << EOF
|
|
{
|
|
"version": "${VERSION}",
|
|
"filename": "wraith.exe",
|
|
"sha256": "${EXE_SHA}",
|
|
"platform": "windows",
|
|
"architecture": "amd64",
|
|
"released": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
"signed": true,
|
|
"dlls": {
|
|
${DLL_ENTRIES} "_note": "All DLLs are EV code-signed"
|
|
}
|
|
}
|
|
EOF
|
|
|
|
echo "=== version.json ==="
|
|
cat dist/version.json
|
|
|
|
# ===============================================================
|
|
# NSIS Installer
|
|
# ===============================================================
|
|
- name: Build NSIS installer
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
|
|
cat > /tmp/wraith-installer.nsi << 'NSIEOF'
|
|
!include "MUI2.nsh"
|
|
|
|
Name "Wraith"
|
|
OutFile "wraith-setup.exe"
|
|
InstallDir "$PROGRAMFILES64\Wraith"
|
|
InstallDirRegKey HKLM "Software\Wraith" "InstallDir"
|
|
RequestExecutionLevel admin
|
|
|
|
!define MUI_ICON "wraith-logo.ico"
|
|
|
|
!insertmacro MUI_PAGE_DIRECTORY
|
|
!insertmacro MUI_PAGE_INSTFILES
|
|
!insertmacro MUI_LANGUAGE "English"
|
|
|
|
Section "Install"
|
|
SetOutPath "$INSTDIR"
|
|
File "wraith.exe"
|
|
File "*.dll"
|
|
File "version.json"
|
|
File "wraith-logo.ico"
|
|
|
|
; Start Menu shortcut
|
|
CreateDirectory "$SMPROGRAMS\Wraith"
|
|
CreateShortcut "$SMPROGRAMS\Wraith\Wraith.lnk" "$INSTDIR\wraith.exe" "" "$INSTDIR\wraith-logo.ico"
|
|
CreateShortcut "$DESKTOP\Wraith.lnk" "$INSTDIR\wraith.exe" "" "$INSTDIR\wraith-logo.ico"
|
|
|
|
; Uninstaller
|
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
|
|
|
; Registry
|
|
WriteRegStr HKLM "Software\Wraith" "InstallDir" "$INSTDIR"
|
|
WriteRegStr HKLM "Software\Wraith" "Version" "${VERSION}"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wraith" "DisplayName" "Wraith"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wraith" "UninstallString" "$INSTDIR\uninstall.exe"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wraith" "DisplayVersion" "${VERSION}"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wraith" "Publisher" "Vantz Stockwell"
|
|
SectionEnd
|
|
|
|
Section "Uninstall"
|
|
Delete "$INSTDIR\wraith.exe"
|
|
Delete "$INSTDIR\*.dll"
|
|
Delete "$INSTDIR\version.json"
|
|
Delete "$INSTDIR\uninstall.exe"
|
|
RMDir "$INSTDIR"
|
|
Delete "$SMPROGRAMS\Wraith\Wraith.lnk"
|
|
RMDir "$SMPROGRAMS\Wraith"
|
|
Delete "$DESKTOP\Wraith.lnk"
|
|
DeleteRegKey HKLM "Software\Wraith"
|
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wraith"
|
|
SectionEnd
|
|
NSIEOF
|
|
|
|
# Replace VERSION placeholder
|
|
sed -i "s/\${VERSION}/${VERSION}/g" /tmp/wraith-installer.nsi
|
|
|
|
# Copy NSIS script and icon into dist so File directives find them
|
|
cp /tmp/wraith-installer.nsi dist/wraith-installer.nsi
|
|
cp images/wraith-logo.ico dist/wraith-logo.ico
|
|
cd dist
|
|
makensis -DVERSION="${VERSION}" wraith-installer.nsi
|
|
mv wraith-setup.exe ../wraith-${VERSION}-setup.exe
|
|
rm wraith-installer.nsi
|
|
cd ..
|
|
|
|
echo "=== Installer built ==="
|
|
ls -la wraith-${VERSION}-setup.exe
|
|
|
|
- name: Sign installer
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
echo "=== Signing installer ==="
|
|
java -jar /usr/local/bin/jsign.jar \
|
|
--storetype AZUREKEYVAULT \
|
|
--keystore "${{ secrets.AZURE_KEY_VAULT_URL }}" \
|
|
--storepass "${{ steps.azure-token.outputs.token }}" \
|
|
--alias "${{ secrets.AZURE_CERT_NAME }}" \
|
|
--tsaurl http://timestamp.digicert.com \
|
|
--tsmode RFC3161 \
|
|
wraith-${VERSION}-setup.exe
|
|
echo "Installer signed."
|
|
|
|
# Move to dist for upload
|
|
mv wraith-${VERSION}-setup.exe dist/
|
|
|
|
# ===============================================================
|
|
# Upload to Gitea Package Registry
|
|
# ===============================================================
|
|
- name: Upload to Gitea packages
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
GITEA_URL="https://git.command.vigilcyber.com"
|
|
OWNER="vstockwell"
|
|
PACKAGE="wraith"
|
|
|
|
echo "=== Uploading Wraith ${VERSION} to Gitea packages ==="
|
|
|
|
# Upload each file as a generic package
|
|
for file in dist/*; do
|
|
[ -f "$file" ] || continue
|
|
FILENAME=$(basename "$file")
|
|
echo "Uploading: ${FILENAME}"
|
|
curl -s -X PUT \
|
|
-H "Authorization: token ${{ secrets.GIT_TOKEN }}" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary @"$file" \
|
|
"${GITEA_URL}/api/packages/${OWNER}/generic/${PACKAGE}/${VERSION}/${FILENAME}"
|
|
echo " Done."
|
|
done
|
|
|
|
echo ""
|
|
echo "=== Upload complete ==="
|
|
echo "Package: ${GITEA_URL}/${OWNER}/-/packages/generic/${PACKAGE}/${VERSION}"
|
|
echo ""
|
|
echo "=== Contents ==="
|
|
ls -la dist/
|