Skip to content
Open
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
43 changes: 43 additions & 0 deletions build-scripts/package-msi
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,36 @@
# MSI packaging uses wixl (GNOME msitools), which builds Windows MSIs natively
# on Linux - no Windows and no Wine.

# Derive a deterministic GUID from a label (ENT-13792)
deterministic_guid() {
printf '{%s}\n' "$(uuidgen --sha1 \
--namespace B883FBCC-6F05-4AFA-98FA-CAF09BF464EA \
--name "$1" | tr '[:lower:]' '[:upper:]')"
}

# Make the Summary Information reproducible: wixl writes a random package code
# (property 9) and the wall-clock build time (12/13 = Created/Last saved).
# Rewrite them to a version-derived package code and SOURCE_DATE_EPOCH.
# (ENT-13792)
normalize_msi_summary() {
msi="$1"
revision="$2"
if [ -z "$SOURCE_DATE_EPOCH" ]; then
log_debug "SOURCE_DATE_EPOCH unset; skipping MSI summary normalization"
return 0
fi
pkgcode=$(deterministic_guid "package:$revision")
datestr=$(TZ=UTC date -u -d "@$SOURCE_DATE_EPOCH" +"%Y/%m/%d %H:%M:%S")
msiinfo export "$msi" _SummaryInformation | tr -d '\r' |
awk -v u="$pkgcode" -v d="$datestr" -F '\t' 'BEGIN { OFS = "\t" }
$1 == "9" { $2 = u }
$1 == "12" { $2 = d }
$1 == "13" { $2 = d }
{ print }' | sed 's/$/\r/' >_summary.idt
msibuild "$msi" -i _summary.idt
rm -f _summary.idt
}

# Determine build directory name based on Jenkins job or version/arch
log_debug "Determining build directory name (JOB_NAME=$JOB_NAME, VERSION=$VERSION, ARCH=$ARCH)"
if [ -z "$JOB_NAME" ]; then
Expand Down Expand Up @@ -85,6 +115,13 @@ pre() {
if [ "$ARCH" = "x86" ]; then
sed -i '/lib\(crypto\|ssl\)/s/[_-]x64//g' "$P"/cfengine-nova.wxs
fi

# Reproducible builds (ENT-13792): pin every packaged file's mtime to
# SOURCE_DATE_EPOCH so the CAB stores a fixed timestamp per file instead
# of the time each file was created.
if [ -n "$SOURCE_DATE_EPOCH" ]; then
find "$P" -exec touch -h -d "@$SOURCE_DATE_EPOCH" {} +
fi
}

# wixl_build() - Build the MSI from the WiX source using wixl (msitools).
Expand All @@ -108,7 +145,13 @@ wixl_build() {
log_debug "Running wixl with REVISION=$REVISION, ARCH=$ARCH"
wixl -a "$wixl_arch" \
-D CfSourceDir=. -D CfVersion="$REVISION" -D CfArch="$ARCH" \
-D CfProductCode="$(deterministic_guid "product:$REVISION")" \
-o cfengine-nova.msi cfengine-nova.wxs

# wixl still bakes a random package code and the wall-clock build time into
# the Summary Information; rewrite them for a byte-reproducible MSI.
# (ENT-13792)
normalize_msi_summary cfengine-nova.msi "$REVISION"
}

# package() - Main packaging function that creates the MSI installer
Expand Down
4 changes: 4 additions & 0 deletions ci/cfengine-build-host-setup.cf
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ bundle agent cfengine_build_host_setup
"wixl";
"msitools";

# build-scripts/package-msi derives deterministic MSI GUIDs with uuidgen
# (uuid-runtime) for reproducible builds. See ENT-13792.
"uuid-runtime";

"binfmt-support"
comment => "update-binfmts command needed for build-scripts/package-msi script";

Expand Down
11 changes: 6 additions & 5 deletions ci/fix-buildhost.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ if [ -f /etc/os-release ]; then
fi

# MinGW hosts build the MSI with wixl (build-scripts/package-msi) and inspect it
# with msiinfo (msitools). Installed by the build-host-setup policy at image
# time; install here too so not-yet-reimaged mingw hosts get them without a
# reimage. See ENT-13868.
# with msiinfo (msitools). uuidgen (uuid-runtime) derives deterministic MSI
# GUIDs for reproducible builds (ENT-13792). Installed by the build-host-setup
# policy at image time; install here too so not-yet-reimaged mingw hosts get
# them without a reimage. See ENT-13868.
if [ -f /etc/cfengine-mingw-build-host.flag ]; then
if ! command -v wixl >/dev/null 2>&1 || ! command -v msiinfo >/dev/null 2>&1; then
if ! command -v wixl >/dev/null 2>&1 || ! command -v msiinfo >/dev/null 2>&1 || ! command -v uuidgen >/dev/null 2>&1; then
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y wixl msitools
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y wixl msitools uuid-runtime
fi
fi
2 changes: 1 addition & 1 deletion container/Dockerfile.mingw
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \
autoconf automake binutils bison build-essential curl debhelper dpkg-dev \
expat fakeroot flex gdb git libexpat1-dev libmodule-load-conditional-perl \
libpam0g-dev libtool libncurses6 libncurses-dev pkg-config psmisc \
python3-pip rsync sudo systemd-coredump unzip wget \
python3-pip rsync sudo systemd-coredump unzip uuid-runtime wget \
&& rm -rf /var/lib/apt/lists/*

# MinGW-w64 cross toolchain.
Expand Down
11 changes: 9 additions & 2 deletions packaging/cfengine-nova/cfengine-nova.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
<?endif?>
<?endif?>

<Product Id='*' Name='CFEngine Nova' Language='1033'
<!-- ProductCode is pinned (not '*') so rebuilds of the same version are
byte-reproducible; package-msi derives it deterministically. ENT-13792 -->
<Product Id='$(var.CfProductCode)' Name='CFEngine Nova' Language='1033'
Version='$(var.CfVersion)' Manufacturer='Northern.tech AS' UpgradeCode='B883FBCC-6F05-4AFA-98FA-CAF09BF464EA' >

<Package Description='CFEngine Nova'
Expand Down Expand Up @@ -262,11 +264,16 @@
<!-- The sequence numbers are weird to avoid collisions with built-in numbers -->

<InstallExecuteSequence>
<RemoveExistingProducts Sequence='1450' />
<InstallInitialize Sequence='1500' />
<Custom Action='GenerateKey' After='InstallFiles'>NOT Installed</Custom>

<InstallFinalize Sequence='6600' />

<!-- After InstallFinalize: install new files first, then remove the old
product, so equal-versioned shared DLLs survive via refcounting. Early
placement deletes them and breaks upgrades. Ref:
https://learn.microsoft.com/en-us/windows/win32/msi/removeexistingproducts-action -->
<RemoveExistingProducts Sequence='6700' />
</InstallExecuteSequence>

</Product>
Expand Down
Loading