Add Windows ARM64 support for host agent (related to #654)

Windows 11 25H2 ships exclusively on ARM64 hardware. When users on ARM64
attempt to install the host agent, the Service Control Manager fails to
load the amd64 binary with ERROR_BAD_EXE_FORMAT, surfaced as "The Pulse
Host Agent is not compatible with this Windows version".

Changes:
- Dockerfile: Build pulse-host-agent-windows-arm64.exe alongside amd64
- Dockerfile: Copy windows-arm64 binary and create symlink for download endpoint
- install-host-agent.ps1: Use RuntimeInformation.OSArchitecture to detect ARM64
- build-release.sh: Build darwin-amd64, darwin-arm64, windows-amd64, windows-arm64
- build-release.sh: Package Windows binaries as .zip archives
- validate-release.sh: Check for windows-arm64 binary and symlink
- validate-release.sh: Add architecture validation for all darwin/windows variants

The installer now correctly detects ARM64 and downloads the appropriate binary.
This commit is contained in:
rcourtman 2025-11-07 12:18:57 +00:00
parent 2a79d57f73
commit 32e0d453c4
5 changed files with 65 additions and 11 deletions

View file

@ -108,7 +108,11 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=${VERSION}" \
-trimpath \
-o pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent
-o pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent && \
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=${VERSION}" \
-trimpath \
-o pulse-host-agent-windows-arm64.exe ./cmd/pulse-host-agent
# Build pulse-sensor-proxy for all Linux architectures (for download endpoint)
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
@ -206,8 +210,10 @@ COPY --from=backend-builder /app/pulse-host-agent-linux-armv7 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-host-agent-darwin-amd64 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-host-agent-darwin-arm64 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-host-agent-windows-amd64.exe /opt/pulse/bin/
# Create symlink for Windows without .exe extension
RUN ln -s pulse-host-agent-windows-amd64.exe /opt/pulse/bin/pulse-host-agent-windows-amd64
COPY --from=backend-builder /app/pulse-host-agent-windows-arm64.exe /opt/pulse/bin/
# Create symlinks for Windows without .exe extension
RUN ln -s pulse-host-agent-windows-amd64.exe /opt/pulse/bin/pulse-host-agent-windows-amd64 && \
ln -s pulse-host-agent-windows-arm64.exe /opt/pulse/bin/pulse-host-agent-windows-arm64
# Copy multi-arch pulse-sensor-proxy binaries for download endpoint
COPY --from=backend-builder /app/pulse-sensor-proxy-linux-amd64 /opt/pulse/bin/

View file

@ -131,7 +131,16 @@ Write-Host " Install Path: $InstallPath"
Write-Host ""
# Determine architecture
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
switch ($osArch) {
'Arm64' { $arch = 'arm64' }
'X64' { $arch = 'amd64' }
'X86' { $arch = '386' }
default {
Write-Error "Unsupported architecture: $osArch"
exit 1
}
}
$downloadUrl = "$PulseUrl/download/pulse-host-agent?platform=windows&arch=$arch"
Write-Info "Downloading agent binary from $downloadUrl..."

View file

@ -235,7 +235,14 @@ chmod +x "$universal_dir/bin/pulse-host-agent"
# Add VERSION file
echo "$VERSION" > "$universal_dir/VERSION"
# Build host agent for macOS arm64
# Build host agent for macOS
echo "Building host agent for macOS amd64..."
env GOOS=darwin GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-host-agent-darwin-amd64" \
./cmd/pulse-host-agent
echo "Building host agent for macOS arm64..."
env GOOS=darwin GOARCH=arm64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
@ -243,8 +250,26 @@ env GOOS=darwin GOARCH=arm64 go build \
-o "$BUILD_DIR/pulse-host-agent-darwin-arm64" \
./cmd/pulse-host-agent
# Package macOS host agent
# Build host agent for Windows
echo "Building host agent for Windows amd64..."
env GOOS=windows GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" \
./cmd/pulse-host-agent
echo "Building host agent for Windows arm64..."
env GOOS=windows GOARCH=arm64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" \
./cmd/pulse-host-agent
# Package standalone host agent binaries
tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-amd64
tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-arm64
zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR/pulse-host-agent-windows-amd64.exe"
zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-host-agent-windows-arm64.exe"
# Create universal tarball
cd "$universal_dir"
@ -268,8 +293,11 @@ for build_name in "${!builds[@]}"; do
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$RELEASE_DIR/"
done
# Also copy standalone macOS host-agent (not tarballed version)
# Also copy standalone macOS and Windows host-agent binaries
cp "$BUILD_DIR/pulse-host-agent-darwin-amd64" "$RELEASE_DIR/"
cp "$BUILD_DIR/pulse-host-agent-darwin-arm64" "$RELEASE_DIR/"
cp "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" "$RELEASE_DIR/"
cp "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" "$RELEASE_DIR/"
# Optionally package Helm chart
if [ "${SKIP_HELM_PACKAGE:-0}" != "1" ]; then

View file

@ -131,7 +131,16 @@ Write-Host " Install Path: $InstallPath"
Write-Host ""
# Determine architecture
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
switch ($osArch) {
'Arm64' { $arch = 'arm64' }
'X64' { $arch = 'amd64' }
'X86' { $arch = '386' }
default {
Write-Error "Unsupported architecture: $osArch"
exit 1
}
}
$downloadUrl = "$PulseUrl/download/pulse-host-agent?platform=windows&arch=$arch"
Write-Info "Downloading agent binary from $downloadUrl..."

View file

@ -72,8 +72,8 @@ success "All installer/uninstaller scripts present and executable"
# Validate all required binaries exist and are non-empty
info "Checking downloadable binaries in /opt/pulse/bin/..."
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/bin; required="pulse pulse-docker-agent pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-arm64 pulse-docker-agent-linux-armv7 pulse-host-agent-linux-amd64 pulse-host-agent-linux-arm64 pulse-host-agent-linux-armv7 pulse-host-agent-darwin-amd64 pulse-host-agent-darwin-arm64 pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 pulse-sensor-proxy pulse-sensor-proxy-linux-amd64 pulse-sensor-proxy-linux-arm64 pulse-sensor-proxy-linux-armv7"; for f in $required; do [ -e "$f" ] || { echo "missing binary $f" >&2; exit 1; }; [ -s "$f" ] || { echo "empty binary $f" >&2; exit 1; }; done; [ "$(readlink pulse-host-agent-windows-amd64)" = "pulse-host-agent-windows-amd64.exe" ] || { echo "windows symlink broken" >&2; exit 1; }; echo "All binaries present"' || { error "Binary validation failed"; exit 1; }
success "All downloadable binaries present (16 binaries + Windows symlink)"
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/bin; required="pulse pulse-docker-agent pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-arm64 pulse-docker-agent-linux-armv7 pulse-host-agent-linux-amd64 pulse-host-agent-linux-arm64 pulse-host-agent-linux-armv7 pulse-host-agent-darwin-amd64 pulse-host-agent-darwin-arm64 pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 pulse-sensor-proxy pulse-sensor-proxy-linux-amd64 pulse-sensor-proxy-linux-arm64 pulse-sensor-proxy-linux-armv7"; for f in $required; do [ -e "$f" ] || { echo "missing binary $f" >&2; exit 1; }; [ -s "$f" ] || { echo "empty binary $f" >&2; exit 1; }; done; [ "$(readlink pulse-host-agent-windows-amd64)" = "pulse-host-agent-windows-amd64.exe" ] || { echo "windows amd64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-host-agent-windows-arm64)" = "pulse-host-agent-windows-arm64.exe" ] || { echo "windows arm64 symlink broken" >&2; exit 1; }; echo "All binaries present"' || { error "Binary validation failed"; exit 1; }
success "All downloadable binaries present (18 binaries + 2 Windows symlinks)"
# Validate version embedding in Docker image binaries
info "Validating version embedding in Docker image binaries..."
@ -191,8 +191,10 @@ if command -v file >/dev/null 2>&1; then
file pulse-host-agent-linux-amd64 | grep -qF 'ELF 64-bit' | grep -qF 'x86-64' || warn "pulse-host-agent-linux-amd64 architecture check failed"
file pulse-host-agent-linux-arm64 | grep -qF 'ELF 64-bit' | grep -qF 'aarch64' || warn "pulse-host-agent-linux-arm64 architecture check failed"
file pulse-host-agent-linux-armv7 | grep -qF 'ELF 32-bit' | grep -qF 'ARM' || warn "pulse-host-agent-linux-armv7 architecture check failed"
file pulse-host-agent-darwin-amd64 | grep -qF 'Mach-O' | grep -qF 'x86_64' || warn "pulse-host-agent-darwin-amd64 architecture check failed"
file pulse-host-agent-darwin-arm64 | grep -qF 'Mach-O' | grep -qF 'arm64' || warn "pulse-host-agent-darwin-arm64 architecture check failed"
file pulse-host-agent-windows-amd64.exe | grep -qF 'PE32+' || warn "pulse-host-agent-windows-amd64.exe architecture check failed"
file pulse-host-agent-windows-amd64.exe | grep -qF 'PE32+' | grep -qF 'x86-64' || warn "pulse-host-agent-windows-amd64.exe architecture check failed"
file pulse-host-agent-windows-arm64.exe | grep -qF 'PE32+' | grep -qF 'Aarch64' || warn "pulse-host-agent-windows-arm64.exe architecture check failed"
success "Binary architectures validated"
fi