Skip to content

1.0.12

1.0.12 #68

Workflow file for this run

name: Build GhostWire Desktop
on:
workflow_dispatch:
release:
types: [published]
env:
BUILD_TYPE: Release
QT_VERSION: 5.15.2
GHOSTWIRE_VERSION: 1.0.6
OPENSSL_VERSION_MACOS: 1.1.1w
MACOS_DEPLOYMENT_TARGET: 11.0
jobs:
build:
name: ${{ matrix.job_name }}
permissions:
contents: write
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- job_name: Windows
os: windows-latest
- job_name: Linux
os: ubuntu-22.04
- job_name: macOS Intel
os: macos-15-intel
macos_arch: x86_64
ghostwire_asset_arch: x86_64
macos_artifact_name: GhostWire-Desktop-macOS-Intel
qt_arch: 'clang_64'
steps:
# Checkout
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Restore config.json from secret
shell: bash
env:
CONFIG_JSON: ${{ secrets.CONFIG_JSON }}
run: |
if [ -z "$CONFIG_JSON" ]; then
echo "ERROR: CONFIG_JSON secret is not set!" >&2
exit 1
fi
mkdir -p resources
printf '%s' "$CONFIG_JSON" > resources/config.json
echo "config.json restored ($(wc -c < resources/config.json) bytes)"
- name: Download ghostwire.dll (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
ASSET="ghostwire-${{ env.GHOSTWIRE_VERSION }}-windows-x86_64.zip"
TMP_DIR="$(mktemp -d)"
mkdir -p libs/ghostwire
curl -fL "https://github.com/momentics/GhostWire.dist/releases/download/${{ env.GHOSTWIRE_VERSION }}/${ASSET}" -o "${TMP_DIR}/${ASSET}"
unzip -q "${TMP_DIR}/${ASSET}" -d "${TMP_DIR}"
DLL_PATH="$(find "${TMP_DIR}" -name "ghostwire.dll" -print -quit)"
if [ -z "$DLL_PATH" ]; then
echo "ERROR: ghostwire.dll not found in ${ASSET}" >&2
exit 1
fi
cp "$DLL_PATH" libs/ghostwire/ghostwire.dll
rm -rf "${TMP_DIR}"
# Linux: Download libghostwire.so
- name: Download libghostwire.so (Linux)
if: runner.os == 'Linux'
run: |
ASSET="ghostwire-${{ env.GHOSTWIRE_VERSION }}-linux-x86_64.tar.gz"
curl -fL "https://github.com/momentics/GhostWire.dist/releases/download/${{ env.GHOSTWIRE_VERSION }}/${ASSET}" -o "/tmp/${ASSET}"
tar -xzf "/tmp/${ASSET}" -C libs/ghostwire/
mv libs/ghostwire/ghostwire-linux-x86_64/libghostwire.so libs/ghostwire/libghostwire.so
rm -rf libs/ghostwire/ghostwire-linux-x86_64
- name: Download OpenSSL 1.1 for Linux (Linux)
if: runner.os == 'Linux'
run: |
mkdir -p libs/openssl/linux
# OpenSSL 1.1.1w for Ubuntu 20.04 (GLIBC 2.31 compatible)
curl -fL "https://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb" -o /tmp/libssl.deb
dpkg-deb -x /tmp/libssl.deb /tmp/openssl-extract
cp /tmp/openssl-extract/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 libs/openssl/linux/
cp /tmp/openssl-extract/usr/lib/x86_64-linux-gnu/libssl.so.1.1 libs/openssl/linux/
rm -rf /tmp/openssl-extract /tmp/libssl.deb
# Linux: Install system dependencies
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libssl-dev
# Linux
- name: Install Qt (Linux)
if: runner.os == 'Linux'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}
- name: Build (Linux)
if: runner.os == 'Linux'
run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel
- name: Package DEB (Linux)
if: runner.os == 'Linux'
run: |
cd build
cpack -C ${{ env.BUILD_TYPE }}
- name: Find DEB file (Linux)
if: runner.os == 'Linux'
run: |
DEB_FILE=$(find build -name "GhostWire*.deb" -print -quit)
if [ -z "$DEB_FILE" ]; then
echo "ERROR: DEB file not found!" >&2
exit 1
fi
echo "DEB_PATH=$DEB_FILE" >> $GITHUB_ENV
echo "DEB_NAME=$(basename $DEB_FILE)" >> $GITHUB_ENV
- name: Package ZIP (Linux)
if: runner.os == 'Linux'
run: |
ZIP_STAGE="$GITHUB_WORKSPACE/build/package-zip-root"
ZIP_FILE="$GITHUB_WORKSPACE/build/${DEB_NAME%.deb}.zip"
rm -rf "$ZIP_STAGE" "$ZIP_FILE"
mkdir -p "$ZIP_STAGE"
DESTDIR="$ZIP_STAGE" cmake --install build --config ${{ env.BUILD_TYPE }}
(
cd "$ZIP_STAGE"
zip -r -y "$ZIP_FILE" .
)
echo "ZIP_PATH=$ZIP_FILE" >> $GITHUB_ENV
echo "ZIP_NAME=$(basename $ZIP_FILE)" >> $GITHUB_ENV
- name: Upload DEB to Release (Linux)
if: runner.os == 'Linux' && github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ env.DEB_PATH }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload ZIP to Release (Linux)
if: runner.os == 'Linux' && github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ env.ZIP_PATH }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload DEB artifact (Linux)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: GhostWire-Desktop-Linux
path: ${{ env.DEB_PATH }}
- name: Upload ZIP artifact (Linux)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: GhostWire-Desktop-Linux-ZIP
path: ${{ env.ZIP_PATH }}
# Windows
- name: Install Qt (Windows)
if: runner.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2019_64'
- name: Install WiX Toolset (Windows)
if: runner.os == 'Windows'
run: |
$wixPath = Get-ChildItem "C:\Program Files (x86)\WiX Toolset v3*" -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
if ($wixPath -and (Test-Path "$($wixPath.FullName)\bin\candle.exe")) {
Write-Host "Found WiX 3 at $($wixPath.FullName)"
echo "$($wixPath.FullName)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
} else {
Write-Host "WiX 3 not found. Installing v3.11.2..."
choco install wixtoolset --version 3.11.2 --no-progress
$wixPath = Get-ChildItem "C:\Program Files (x86)\WiX Toolset v3*" -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
if ($wixPath) {
echo "$($wixPath.FullName)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
} else {
Write-Error "WiX installation failed."
exit 1
}
}
Get-Command candle -ErrorAction Stop
Get-Command light -ErrorAction Stop
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}
- name: Build (Windows)
if: runner.os == 'Windows'
run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel
- name: Package MSI (Windows)
if: runner.os == 'Windows'
run: cpack --config build/CPackConfig.cmake -C ${{ env.BUILD_TYPE }}
- name: Show WiX Log on Failure
if: failure() && runner.os == 'Windows'
run: |
$log = "_CPack_Packages/win64/WIX/wix.log"
if (Test-Path $log) {
Write-Host "=== WiX Error Log ==="
Get-Content $log
}
- name: Find MSI file (Windows)
if: runner.os == 'Windows'
run: |
$msi = Get-ChildItem -Path . -Filter "*.msi" -Recurse | Select-Object -First 1
if ($msi) {
echo "MSI_PATH=$($msi.FullName)" >> $env:GITHUB_ENV
echo "MSI_NAME=$($msi.Name)" >> $env:GITHUB_ENV
} else {
Write-Error "MSI file not found!"
exit 1
}
- name: Package ZIP (Windows)
if: runner.os == 'Windows'
run: |
$zipStage = Join-Path $env:GITHUB_WORKSPACE "build\package-zip-root"
$zipFile = Join-Path $env:GITHUB_WORKSPACE "build\$($env:MSI_NAME -replace '\.msi$', '.zip')"
Remove-Item -Recurse -Force $zipStage, $zipFile -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $zipStage | Out-Null
cmake --install build --config $env:BUILD_TYPE --prefix $zipStage
$requiredFiles = @(
"GhostWireDesktop.exe",
"ghostwire.dll",
"libcrypto-1_1-x64.dll",
"libssl-1_1-x64.dll"
)
foreach ($file in $requiredFiles) {
$path = Join-Path $zipStage $file
if (!(Test-Path $path)) {
Write-Error "Required ZIP payload file is missing: $file"
exit 1
}
}
Compress-Archive -Path (Join-Path $zipStage "*") -DestinationPath $zipFile -Force
echo "WIN_ZIP_PATH=$zipFile" >> $env:GITHUB_ENV
echo "WIN_ZIP_NAME=$(Split-Path $zipFile -Leaf)" >> $env:GITHUB_ENV
- name: Upload MSI to Release (Windows)
if: runner.os == 'Windows' && github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ env.MSI_PATH }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload ZIP to Release (Windows)
if: runner.os == 'Windows' && github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ env.WIN_ZIP_PATH }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload MSI artifact (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: GhostWire-Desktop-Windows
path: ${{ env.MSI_PATH }}
- name: Upload ZIP artifact (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: GhostWire-Desktop-Windows-ZIP
path: ${{ env.WIN_ZIP_PATH }}
# macOS
- name: Download libghostwire.dylib (macOS)
if: runner.os == 'macOS'
run: |
ASSET="ghostwire-${{ env.GHOSTWIRE_VERSION }}-macos-${{ matrix.ghostwire_asset_arch }}.tar.gz"
TMP_DIR="$(mktemp -d)"
mkdir -p libs/ghostwire
curl -fL "https://github.com/momentics/GhostWire.dist/releases/download/${{ env.GHOSTWIRE_VERSION }}/${ASSET}" -o "${TMP_DIR}/${ASSET}"
tar -xzf "${TMP_DIR}/${ASSET}" -C "${TMP_DIR}"
DYLIB_PATH="$(find "${TMP_DIR}" -name "libghostwire.dylib" -print -quit)"
if [ -z "$DYLIB_PATH" ]; then
echo "ERROR: libghostwire.dylib not found in ${ASSET}" >&2
exit 1
fi
cp "$DYLIB_PATH" libs/ghostwire/libghostwire.dylib
rm -rf "${TMP_DIR}"
- name: Install OpenSSL (macOS)
if: runner.os == 'macOS'
run: |
export MACOSX_DEPLOYMENT_TARGET="${{ env.MACOS_DEPLOYMENT_TARGET }}"
mkdir -p libs/openssl/macos
OPENSSL_TARGET="darwin64-${{ matrix.macos_arch }}-cc"
OPENSSL_ARCHIVE="openssl-${{ env.OPENSSL_VERSION_MACOS }}.tar.gz"
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/${OPENSSL_ARCHIVE}"
TMP_DIR="$(mktemp -d)"
curl -fL "${OPENSSL_URL}" -o "${TMP_DIR}/${OPENSSL_ARCHIVE}"
tar -xzf "${TMP_DIR}/${OPENSSL_ARCHIVE}" -C "${TMP_DIR}"
pushd "${TMP_DIR}/openssl-${{ env.OPENSSL_VERSION_MACOS }}"
./Configure "${OPENSSL_TARGET}" shared no-tests --prefix="${TMP_DIR}/openssl-install"
make -j"$(sysctl -n hw.ncpu)"
make install_sw
popd
cp "${TMP_DIR}/openssl-install/lib/libcrypto.1.1.dylib" libs/openssl/macos/
cp "${TMP_DIR}/openssl-install/lib/libssl.1.1.dylib" libs/openssl/macos/
OPENSSL_LICENSE="$(find "${TMP_DIR}/openssl-${{ env.OPENSSL_VERSION_MACOS }}" -maxdepth 1 -type f \( -name 'LICENSE' -o -name 'LICENSE.txt' \) | head -n 1)"
if [ -z "$OPENSSL_LICENSE" ]; then
echo "ERROR: OpenSSL license file not found" >&2
exit 1
fi
cp "${OPENSSL_LICENSE}" libs/openssl/macos/license.txt
rm -rf "${TMP_DIR}"
- name: Install Qt (macOS)
if: runner.os == 'macOS'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: ${{ matrix.qt_arch }}
- name: Configure CMake (macOS)
if: runner.os == 'macOS'
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_OSX_ARCHITECTURES=${{ matrix.macos_arch }} -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ env.MACOS_DEPLOYMENT_TARGET }}
- name: Build (macOS)
if: runner.os == 'macOS'
run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel
- name: Verify macOS deployment target
if: runner.os == 'macOS'
run: |
python3 - <<'PY'
import os
import re
import subprocess
import sys
from pathlib import Path
app = Path("build/GhostWireDesktop.app")
if not app.exists():
sys.exit(f"App bundle not found: {app}")
def parse_version(value):
parts = [int(part) for part in value.split(".")]
return tuple(parts + [0] * (3 - len(parts)))
target = parse_version(os.environ["MACOS_DEPLOYMENT_TARGET"])
def version_text(value):
parts = list(value)
while len(parts) > 1 and parts[-1] == 0:
parts.pop()
return ".".join(str(part) for part in parts)
macho_files = []
for path in app.rglob("*"):
if not path.is_file() or path.is_symlink():
continue
description = subprocess.run(
["file", str(path)],
check=True,
text=True,
capture_output=True,
).stdout
if "Mach-O" in description:
macho_files.append(path)
if not macho_files:
sys.exit(f"No Mach-O files found in {app}")
failures = []
for path in sorted(macho_files):
output = subprocess.run(
["otool", "-l", str(path)],
check=True,
text=True,
capture_output=True,
).stdout
versions = []
for block in re.split(r"(?=^Load command \d+$)", output, flags=re.MULTILINE):
if re.search(r"^\s*cmd\s+LC_BUILD_VERSION$", block, re.MULTILINE):
match = re.search(r"^\s*minos\s+([0-9]+(?:\.[0-9]+){1,2})", block, re.MULTILINE)
if match:
versions.append(parse_version(match.group(1)))
elif re.search(r"^\s*cmd\s+LC_VERSION_MIN_MACOSX$", block, re.MULTILINE):
match = re.search(r"^\s*version\s+([0-9]+(?:\.[0-9]+){1,2})", block, re.MULTILINE)
if match:
versions.append(parse_version(match.group(1)))
if not versions:
failures.append((path, "missing deployment target load command"))
continue
too_new = [version for version in versions if version > target]
if too_new:
failures.append((path, ", ".join(version_text(version) for version in too_new)))
else:
print(f"OK {path}: min {', '.join(version_text(version) for version in sorted(set(versions)))}")
if failures:
print(f"Mach-O files requiring macOS newer than {os.environ['MACOS_DEPLOYMENT_TARGET']}:", file=sys.stderr)
for path, detail in failures:
print(f" {path}: {detail}", file=sys.stderr)
sys.exit(1)
PY
- name: Package DMG (macOS)
if: runner.os == 'macOS'
run: |
cd build
cpack -C ${{ env.BUILD_TYPE }}
- name: Find DMG file (macOS)
if: runner.os == 'macOS'
run: |
DMG_FILE=$(find build -name "GhostWire*.dmg" -print -quit)
if [ -z "$DMG_FILE" ]; then
echo "ERROR: DMG file not found!" >&2
exit 1
fi
echo "DMG_PATH=$DMG_FILE" >> $GITHUB_ENV
echo "DMG_NAME=$(basename $DMG_FILE)" >> $GITHUB_ENV
- name: Upload DMG to Release (macOS)
if: runner.os == 'macOS' && github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: ${{ env.DMG_PATH }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload DMG artifact (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.macos_artifact_name }}
path: ${{ env.DMG_PATH }}