Build fleetctl MSI packages (#43403)
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
name: Build fleetctl Windows MSI
|
||||
|
||||
# This workflow builds a signed Windows .msi installer for fleetctl
|
||||
# for manual testing purposes only.
|
||||
#
|
||||
# NOTE: For production releases, the goreleaser workflow (.github/workflows/goreleaser-fleet.yaml)
|
||||
# handles building and uploading the fleetctl MSI automatically when a fleet-* tag is pushed.
|
||||
# This workflow is kept for manual testing only.
|
||||
#
|
||||
# TESTING:
|
||||
# To test the MSI build process:
|
||||
# 1. Go to Actions -> Build fleetctl Windows MSI -> Run workflow
|
||||
# 2. Select your branch (e.g., main or feature branch)
|
||||
# 3. Test mode defaults to true (recommended) - MSI will be built and uploaded as an artifact
|
||||
# 4. To test release upload, set test_mode to false (use with caution)
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger for testing only
|
||||
inputs:
|
||||
test_mode:
|
||||
description: "Test mode - will skip release upload if enabled (recommended: true)"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: write # Needed to upload release assets
|
||||
id-token: write # Needed for attestations
|
||||
attestations: write # Needed to create build provenance attestations
|
||||
|
||||
jobs:
|
||||
build-sign-msi:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, arm64]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract version
|
||||
id: extract_version
|
||||
run: |
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
|
||||
# Check if running from a tag
|
||||
if [[ "${{ github.ref }}" == refs/tags/* ]] && [[ "$REF_NAME" == fleet-* ]]; then
|
||||
VERSION="${REF_NAME#fleet-}"
|
||||
VERSION="${VERSION#v}"
|
||||
TAG_NAME="$REF_NAME"
|
||||
else
|
||||
VERSION="test-$(date +%Y%m%d-%H%M%S)"
|
||||
TAG_NAME="fleet-${VERSION}"
|
||||
fi
|
||||
|
||||
# For MSI, we need a numeric version (X.Y.Z). Use 0.0.0 for test builds.
|
||||
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
MSI_VERSION="$VERSION"
|
||||
else
|
||||
MSI_VERSION="0.0.0"
|
||||
fi
|
||||
|
||||
# Determine test mode
|
||||
if [ "${{ github.event.inputs.test_mode }}" = "false" ]; then
|
||||
IS_TEST_MODE="false"
|
||||
else
|
||||
IS_TEST_MODE="true"
|
||||
fi
|
||||
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "msi_version=$MSI_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||
echo "is_test_mode=$IS_TEST_MODE" >> $GITHUB_OUTPUT
|
||||
echo "Fleet version: $VERSION (MSI version: $MSI_VERSION)"
|
||||
if [ "$IS_TEST_MODE" = "true" ]; then
|
||||
echo "TEST MODE: MSI will be built but NOT uploaded to release"
|
||||
else
|
||||
echo "PRODUCTION MODE: MSI will be uploaded to release $TAG_NAME"
|
||||
fi
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Build fleetctl binary
|
||||
run: |
|
||||
VERSION="${{ steps.extract_version.outputs.version }}"
|
||||
|
||||
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
BRANCH_NAME="${{ steps.extract_version.outputs.tag_name }}"
|
||||
else
|
||||
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
|
||||
fi
|
||||
|
||||
LDFLAGS="-X github.com/fleetdm/fleet/v4/server/version.appName=fleetctl \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.version=${VERSION} \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.branch=${BRANCH_NAME} \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.revision=${GITHUB_SHA} \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.buildDate=$(date -u +%Y-%m-%d) \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.buildUser=github-actions"
|
||||
|
||||
echo "Building fleetctl for windows/${{ matrix.arch }}..."
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=${{ matrix.arch }} go build \
|
||||
-trimpath \
|
||||
-ldflags "$LDFLAGS" \
|
||||
-o fleetctl.exe \
|
||||
./cmd/fleetctl
|
||||
|
||||
if [ ! -f fleetctl.exe ]; then
|
||||
echo "Error: fleetctl.exe not found after build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Built fleetctl.exe ($(wc -c < fleetctl.exe) bytes)"
|
||||
|
||||
- name: Install WiX 3.14.1
|
||||
run: |
|
||||
curl -fSL -o wix314-binaries.zip \
|
||||
https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip
|
||||
mkdir -p "$RUNNER_TEMP/wix"
|
||||
unzip -q wix314-binaries.zip -d "$RUNNER_TEMP/wix"
|
||||
echo "$RUNNER_TEMP/wix" >> $GITHUB_PATH
|
||||
|
||||
- name: Setup DigiCert KeyLocker
|
||||
env:
|
||||
DIGICERT_KEYLOCKER_CERTIFICATE: ${{ secrets.DIGICERT_KEYLOCKER_CERTIFICATE }}
|
||||
DIGICERT_KEYLOCKER_PASSWORD: ${{ secrets.DIGICERT_KEYLOCKER_PASSWORD }}
|
||||
DIGICERT_KEYLOCKER_HOST_URL: ${{ secrets.DIGICERT_KEYLOCKER_HOST_URL }}
|
||||
DIGICERT_API_KEY: ${{ secrets.DIGICERT_API_KEY }}
|
||||
run: |
|
||||
# Decode certificate
|
||||
echo "$DIGICERT_KEYLOCKER_CERTIFICATE" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
|
||||
# Set environment variables for subsequent steps
|
||||
echo "SM_HOST=$DIGICERT_KEYLOCKER_HOST_URL" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=$DIGICERT_API_KEY" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=$DIGICERT_KEYLOCKER_PASSWORD" >> "$GITHUB_ENV"
|
||||
|
||||
# Add signing tools to PATH
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH
|
||||
|
||||
- name: Install DigiCert KeyLocker KSP
|
||||
run: |
|
||||
curl https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" --fail-with-body -o Keylockertools-windows-x64.msi
|
||||
msiexec /i Keylockertools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
shell: cmd
|
||||
|
||||
- name: Sync certificates
|
||||
run: |
|
||||
smctl windows certsync
|
||||
shell: cmd
|
||||
|
||||
- name: Build and sign MSI
|
||||
env:
|
||||
DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT: ${{ secrets.DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT }}
|
||||
SKIP_UPLOAD: ${{ inputs.test_mode && 'true' || 'false' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
chmod +x tools/build-fleetctl-msi/main.sh
|
||||
./tools/build-fleetctl-msi/main.sh \
|
||||
fleetctl.exe \
|
||||
"${{ steps.extract_version.outputs.msi_version }}" \
|
||||
"${{ matrix.arch }}"
|
||||
|
||||
- name: Upload MSI artifact (test mode)
|
||||
if: ${{ inputs.test_mode }}
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: fleetctl-msi-${{ matrix.arch }}
|
||||
path: dist/fleetctl_v${{ steps.extract_version.outputs.msi_version }}_windows_${{ matrix.arch }}.msi
|
||||
retention-days: 7
|
||||
|
||||
- name: Attest MSI
|
||||
continue-on-error: true
|
||||
uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0
|
||||
with:
|
||||
subject-path: dist/fleetctl_v${{ steps.extract_version.outputs.msi_version }}_windows_${{ matrix.arch }}.msi
|
||||
push-to-registry: false
|
||||
@@ -22,6 +22,8 @@ jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-22.04-4-cores
|
||||
environment: Docker Hub
|
||||
outputs:
|
||||
windows_msi_arches: ${{ steps.check_windows_binaries.outputs.arches }}
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
@@ -156,3 +158,187 @@ jobs:
|
||||
docker tag fleetdm/fleet:${TAG} quay.io/fleetdm/fleet:${TAG}
|
||||
docker push quay.io/fleetdm/fleet:${TAG}
|
||||
done
|
||||
|
||||
- name: Check if Windows fleetctl binaries exist
|
||||
id: check_windows_binaries
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Goreleaser v2 places binaries at dist/<id>_<os>_<arch>*/binary
|
||||
ARCHES=()
|
||||
for arch in amd64 arm64; do
|
||||
EXE=$(find dist -name "fleetctl.exe" -path "*windows*${arch}*" -print -quit 2>/dev/null)
|
||||
if [[ -n "$EXE" ]]; then
|
||||
echo "Found: $EXE"
|
||||
echo "windows_${arch}_path=$EXE" >> "$GITHUB_OUTPUT"
|
||||
ARCHES+=("\"$arch\"")
|
||||
else
|
||||
echo "Warning: No fleetctl.exe found for windows/$arch"
|
||||
fi
|
||||
done
|
||||
echo "arches=[$(IFS=,; echo "${ARCHES[*]}")]" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload unsigned Windows fleetctl amd64
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
if: steps.check_windows_binaries.outputs.windows_amd64_path != ''
|
||||
with:
|
||||
name: fleetctl-unsigned-windows-amd64
|
||||
path: ${{ steps.check_windows_binaries.outputs.windows_amd64_path }}
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload unsigned Windows fleetctl arm64
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
if: steps.check_windows_binaries.outputs.windows_arm64_path != ''
|
||||
with:
|
||||
name: fleetctl-unsigned-windows-arm64
|
||||
path: ${{ steps.check_windows_binaries.outputs.windows_arm64_path }}
|
||||
retention-days: 1
|
||||
|
||||
build-fleetctl-msi:
|
||||
runs-on: windows-2022
|
||||
needs: goreleaser
|
||||
if: needs.goreleaser.outputs.windows_msi_arches != '[]'
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ${{ fromJSON(needs.goreleaser.outputs.windows_msi_arches) }}
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Download unsigned fleetctl binary
|
||||
uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.8
|
||||
with:
|
||||
name: fleetctl-unsigned-windows-${{ matrix.arch }}
|
||||
|
||||
- name: Verify binary exists
|
||||
run: |
|
||||
if [[ ! -f "fleetctl.exe" ]]; then
|
||||
echo "Error: fleetctl.exe not found after download"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
echo "Binary found ($(wc -c < fleetctl.exe) bytes)"
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: |
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
VERSION="${TAG_NAME#fleet-}"
|
||||
VERSION="${VERSION#v}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Install WiX 3.14.1
|
||||
run: |
|
||||
curl -fSL -o wix314-binaries.zip \
|
||||
https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip
|
||||
mkdir -p "$RUNNER_TEMP/wix"
|
||||
unzip -q wix314-binaries.zip -d "$RUNNER_TEMP/wix"
|
||||
echo "$RUNNER_TEMP/wix" >> $GITHUB_PATH
|
||||
|
||||
- name: Setup DigiCert KeyLocker
|
||||
env:
|
||||
DIGICERT_KEYLOCKER_CERTIFICATE: ${{ secrets.DIGICERT_KEYLOCKER_CERTIFICATE }}
|
||||
DIGICERT_KEYLOCKER_PASSWORD: ${{ secrets.DIGICERT_KEYLOCKER_PASSWORD }}
|
||||
DIGICERT_KEYLOCKER_HOST_URL: ${{ secrets.DIGICERT_KEYLOCKER_HOST_URL }}
|
||||
DIGICERT_API_KEY: ${{ secrets.DIGICERT_API_KEY }}
|
||||
run: |
|
||||
echo "$DIGICERT_KEYLOCKER_CERTIFICATE" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
echo "SM_HOST=$DIGICERT_KEYLOCKER_HOST_URL" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=$DIGICERT_API_KEY" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=$DIGICERT_KEYLOCKER_PASSWORD" >> "$GITHUB_ENV"
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH
|
||||
|
||||
- name: Install DigiCert KeyLocker KSP
|
||||
run: |
|
||||
curl https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" --fail-with-body -o Keylockertools-windows-x64.msi
|
||||
msiexec /i Keylockertools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
shell: cmd
|
||||
|
||||
- name: Sync certificates
|
||||
run: |
|
||||
smctl windows certsync
|
||||
shell: cmd
|
||||
|
||||
- name: Build and sign MSI
|
||||
env:
|
||||
DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT: ${{ secrets.DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT }}
|
||||
SKIP_UPLOAD: "true"
|
||||
run: |
|
||||
chmod +x tools/build-fleetctl-msi/main.sh
|
||||
./tools/build-fleetctl-msi/main.sh \
|
||||
fleetctl.exe \
|
||||
"${{ steps.version.outputs.version }}" \
|
||||
"${{ matrix.arch }}"
|
||||
|
||||
- name: Upload signed MSI artifact
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: fleetctl-msi-${{ matrix.arch }}
|
||||
path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ matrix.arch }}.msi
|
||||
retention-days: 1
|
||||
|
||||
- name: Attest MSI
|
||||
continue-on-error: true
|
||||
uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0
|
||||
with:
|
||||
subject-path: dist/fleetctl_v${{ steps.version.outputs.version }}_windows_${{ matrix.arch }}.msi
|
||||
|
||||
upload-fleetctl-msi:
|
||||
needs: build-fleetctl-msi
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download signed MSI artifacts
|
||||
uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.8
|
||||
with:
|
||||
pattern: fleetctl-msi-*
|
||||
merge-multiple: true
|
||||
path: dist
|
||||
|
||||
- name: Upload MSIs to release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
echo "Uploading MSIs to release $TAG_NAME..."
|
||||
|
||||
# Wait for release to exist
|
||||
MAX_WAIT=300
|
||||
ELAPSED=0
|
||||
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
||||
if gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" &>/dev/null; then
|
||||
echo "Release found"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for release... (${ELAPSED}s/${MAX_WAIT}s)"
|
||||
sleep 10
|
||||
ELAPSED=$((ELAPSED + 10))
|
||||
done
|
||||
|
||||
for msi in dist/*.msi; do
|
||||
echo "Uploading $msi..."
|
||||
gh release upload "$TAG_NAME" "$msi" --repo "$GITHUB_REPOSITORY" --clobber
|
||||
done
|
||||
echo "Upload complete"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- `fleetctl` is now also released as an `msi` for Windows
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="fleetctl"
|
||||
Language="1033"
|
||||
Version="__VERSION__"
|
||||
Manufacturer="Fleet Device Management (fleetdm.com)"
|
||||
UpgradeCode="DA27C96F-3832-4E04-A6A9-1FDE1A8B37BE">
|
||||
|
||||
<Package
|
||||
Keywords="fleetctl"
|
||||
Description="Fleet device management CLI tool"
|
||||
InstallerVersion="500"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
InstallPrivileges="elevated"
|
||||
Languages="1033" />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage="A newer version of fleetctl is already installed."
|
||||
AllowSameVersionUpgrades="yes" />
|
||||
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" />
|
||||
<Property Id="ARPNOMODIFY" Value="yes" Secure="yes" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="FLEETDMDIR" Name="FleetDM">
|
||||
<Directory Id="INSTALLDIR" Name="fleetctl">
|
||||
<Component Id="C_FleetctlExe" Guid="E8A1B2C3-D4E5-6F78-9A0B-1C2D3E4F5A6B">
|
||||
<File Id="FleetctlExe" Source="root\fleetctl.exe" KeyPath="yes" />
|
||||
<Environment Id="PATH"
|
||||
Name="PATH"
|
||||
Value="[INSTALLDIR]"
|
||||
Permanent="no"
|
||||
Part="last"
|
||||
Action="set"
|
||||
System="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id="FleetctlFeature" Title="fleetctl" Level="1">
|
||||
<ComponentRef Id="C_FleetctlExe" />
|
||||
</Feature>
|
||||
</Product>
|
||||
</Wix>
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
# This script builds a signed Windows .msi installer for fleetctl.
|
||||
# It expects to run on a Windows GitHub Actions runner with Git Bash,
|
||||
# with DigiCert KeyLocker already configured (env vars set, KSP installed,
|
||||
# certs synced) and WiX 3.14 tools on PATH.
|
||||
#
|
||||
# Usage: ./main.sh <path-to-fleetctl.exe> <version> <arch>
|
||||
# version: semver like "4.72.0" or "v4.72.0" (v prefix stripped)
|
||||
# arch: "amd64" or "arm64"
|
||||
#
|
||||
# Environment variables:
|
||||
# DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT - SHA1 fingerprint for signtool
|
||||
# SKIP_UPLOAD - set to "true" to skip GitHub release upload (default: skip)
|
||||
# GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_REF - required if uploading to release
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
check_env_var() {
|
||||
if [[ -z "${!1}" ]]; then
|
||||
echo "Error: Environment variable $1 not set."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
FLEETCTL_EXE="$1"
|
||||
VERSION="$2"
|
||||
ARCH="${3:-amd64}"
|
||||
|
||||
if [[ -z "$FLEETCTL_EXE" ]] || [[ -z "$VERSION" ]]; then
|
||||
echo "Usage: $0 <path-to-fleetctl.exe> <version> [arch]"
|
||||
echo " version: semver like 4.72.0 or v4.72.0"
|
||||
echo " arch: amd64 (default) or arm64"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$FLEETCTL_EXE" ]]; then
|
||||
echo "Error: fleetctl.exe not found at $FLEETCTL_EXE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check required env var for signing
|
||||
check_env_var "DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT"
|
||||
|
||||
# Strip v prefix from version
|
||||
VERSION="${VERSION#v}"
|
||||
|
||||
# Validate version format (WiX requires X.Y.Z or X.Y.Z.W, each component 0-65535)
|
||||
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
echo "Error: Version '$VERSION' is not a valid MSI version (expected X.Y.Z or X.Y.Z.W)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Map Go arch to WiX candle arch
|
||||
case "$ARCH" in
|
||||
amd64) WIX_ARCH="x64" ;;
|
||||
arm64) WIX_ARCH="arm64" ;;
|
||||
*)
|
||||
echo "Error: Unsupported architecture '$ARCH' (expected amd64 or arm64)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
MSI_NAME="fleetctl_v${VERSION}_windows_${ARCH}.msi"
|
||||
echo "Building $MSI_NAME..."
|
||||
|
||||
# Create working directory
|
||||
WORKDIR="$(mktemp -d)"
|
||||
# Convert to Windows path for native Windows tools (signtool, candle, light)
|
||||
WORKDIR_WIN="$(cygpath -w "$WORKDIR")"
|
||||
cleanup() {
|
||||
echo "Cleaning up..."
|
||||
rm -rf "$WORKDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Set up working directory structure
|
||||
mkdir -p "$WORKDIR/root"
|
||||
cp "$FLEETCTL_EXE" "$WORKDIR/root/fleetctl.exe"
|
||||
|
||||
# Substitute version placeholder in WiX template
|
||||
sed "s/__VERSION__/$VERSION/g" "$SCRIPT_DIR/fleetctl.wxs" > "$WORKDIR/main.wxs"
|
||||
|
||||
# Sign fleetctl.exe before packaging into MSI
|
||||
# MSYS_NO_PATHCONV=1 prevents Git Bash from converting /flags to Windows paths
|
||||
# Windows-native tools need Windows-style paths (WORKDIR_WIN)
|
||||
echo "Signing fleetctl.exe..."
|
||||
MSYS_NO_PATHCONV=1 signtool.exe sign /v /sha1 "$DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT" \
|
||||
/tr http://timestamp.digicert.com /td SHA256 /fd SHA256 \
|
||||
/d "fleetctl by Fleet Device Management" /du "https://fleetdm.com" \
|
||||
"$WORKDIR_WIN\\root\\fleetctl.exe"
|
||||
MSYS_NO_PATHCONV=1 signtool.exe verify /v /pa "$WORKDIR_WIN\\root\\fleetctl.exe"
|
||||
echo "fleetctl.exe signed successfully"
|
||||
|
||||
# Compile WiX source
|
||||
echo "Running candle (WiX compiler)..."
|
||||
candle.exe "$WORKDIR_WIN\\main.wxs" -arch "$WIX_ARCH" -out "$WORKDIR_WIN\\main.wixobj"
|
||||
|
||||
# Link to create MSI
|
||||
echo "Running light (WiX linker)..."
|
||||
light.exe "$WORKDIR_WIN\\main.wixobj" -b "$WORKDIR_WIN" -out "$WORKDIR_WIN\\$MSI_NAME" -sval
|
||||
|
||||
# Sign the MSI
|
||||
echo "Signing MSI..."
|
||||
MSYS_NO_PATHCONV=1 signtool.exe sign /v /sha1 "$DIGICERT_KEYLOCKER_CERTIFICATE_FINGERPRINT" \
|
||||
/tr http://timestamp.digicert.com /td SHA256 /fd SHA256 \
|
||||
/d "fleetctl by Fleet Device Management" /du "https://fleetdm.com" \
|
||||
"$WORKDIR_WIN\\$MSI_NAME"
|
||||
MSYS_NO_PATHCONV=1 signtool.exe verify /v /pa "$WORKDIR_WIN\\$MSI_NAME"
|
||||
echo "MSI signed successfully"
|
||||
|
||||
# Copy to output location
|
||||
mkdir -p dist
|
||||
cp "$WORKDIR/$MSI_NAME" "dist/$MSI_NAME"
|
||||
echo "Package created: dist/$MSI_NAME"
|
||||
|
||||
# Upload to release unless skipped
|
||||
if [[ "$SKIP_UPLOAD" == "true" ]]; then
|
||||
echo "Skipping release upload (SKIP_UPLOAD=true)"
|
||||
elif [[ -n "$GITHUB_TOKEN" ]] && [[ -n "$GITHUB_REPOSITORY" ]] && [[ -n "$GITHUB_REF" ]] && command -v gh &> /dev/null; then
|
||||
if [[ "$GITHUB_REF" != refs/tags/* ]]; then
|
||||
echo "Error: GITHUB_REF is not a tag ref ($GITHUB_REF), cannot upload to release"
|
||||
exit 1
|
||||
fi
|
||||
TAG_NAME="${GITHUB_REF#refs/tags/}"
|
||||
echo "Uploading package to release $TAG_NAME..."
|
||||
|
||||
# Wait for release to exist (goreleaser creates it as draft)
|
||||
MAX_WAIT=300
|
||||
ELAPSED=0
|
||||
INTERVAL=10
|
||||
|
||||
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
||||
if gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" &>/dev/null; then
|
||||
echo "Release found, uploading package..."
|
||||
break
|
||||
fi
|
||||
echo "Waiting for release to be created... (${ELAPSED}s/${MAX_WAIT}s)"
|
||||
sleep $INTERVAL
|
||||
ELAPSED=$((ELAPSED + INTERVAL))
|
||||
done
|
||||
|
||||
if [ $ELAPSED -ge $MAX_WAIT ]; then
|
||||
echo "Warning: Release not found after waiting ${MAX_WAIT}s"
|
||||
echo "Attempting upload anyway..."
|
||||
fi
|
||||
|
||||
echo "Uploading dist/$MSI_NAME to release $TAG_NAME"
|
||||
gh release upload "$TAG_NAME" "dist/$MSI_NAME" --repo "$GITHUB_REPOSITORY" --clobber || {
|
||||
echo "Upload failed, retrying once..."
|
||||
sleep 5
|
||||
gh release upload "$TAG_NAME" "dist/$MSI_NAME" --repo "$GITHUB_REPOSITORY" --clobber || {
|
||||
echo "Failed to upload package after retry"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
echo "Package uploaded successfully: dist/$MSI_NAME"
|
||||
else
|
||||
echo "Skipping release upload (GITHUB_TOKEN or gh not available)"
|
||||
fi
|
||||
Reference in New Issue
Block a user