Pulse/scripts/build-release.sh
rcourtman c638a8c28c Fix checksum verification failure during installation
Related to #639

Users reported "Failed to download checksum for Pulse release" errors
during installation. The root cause was a mismatch between what the
build system generates and what the installer expects:

- install.sh downloads individual .sha256 files (e.g., pulse-v4.25.0-linux-amd64.tar.gz.sha256)
- build-release.sh only created a single checksums.txt file

This commit updates build-release.sh to generate both:
1. Individual .sha256 files for each asset (required by install.sh)
2. Combined checksums.txt for manual verification and signing

This maintains backwards compatibility with the installer while keeping
the aggregated checksums.txt for power users and GPG signing.
2025-11-06 11:21:49 +00:00

298 lines
9.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# Build script for Pulse releases
# Creates release archives for different architectures
set -euo pipefail
# Use Go 1.24 if available
if [ -x /usr/local/go/bin/go ]; then
export PATH=/usr/local/go/bin:$PATH
fi
# Force static binaries so release artifacts run on older glibc hosts
export CGO_ENABLED=0
VERSION=${1:-$(cat VERSION)}
BUILD_DIR="build"
RELEASE_DIR="release"
echo "Building Pulse v${VERSION}..."
# Clean previous builds
rm -rf $BUILD_DIR $RELEASE_DIR
mkdir -p $BUILD_DIR $RELEASE_DIR
# Build frontend
echo "Building frontend..."
npm --prefix frontend-modern ci
npm --prefix frontend-modern run build
# Copy frontend dist for embedding (required for Go embed)
echo "Copying frontend dist for embedding..."
rm -rf internal/api/frontend-modern
mkdir -p internal/api/frontend-modern
cp -r frontend-modern/dist internal/api/frontend-modern/
# Build for different architectures
declare -A builds=(
["linux-amd64"]="GOOS=linux GOARCH=amd64"
["linux-arm64"]="GOOS=linux GOARCH=arm64"
["linux-armv7"]="GOOS=linux GOARCH=arm GOARM=7"
)
for build_name in "${!builds[@]}"; do
echo "Building for $build_name..."
# Get build environment
build_env="${builds[$build_name]}"
build_time=$(date -u '+%Y-%m-%d_%H:%M:%S')
git_commit=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')
# Build backend binary with version info
env $build_env go build \
-ldflags="-s -w -X main.Version=v${VERSION} -X main.BuildTime=${build_time} -X main.GitCommit=${git_commit} -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-$build_name" \
./cmd/pulse
# Build docker agent binary
env $build_env go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-docker-agent-$build_name" \
./cmd/pulse-docker-agent
# Build host agent binary
env $build_env go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-host-agent-$build_name" \
./cmd/pulse-host-agent
# Build temperature proxy binary
env $build_env go build \
-ldflags="-s -w -X main.Version=v${VERSION} -X main.BuildTime=${build_time} -X main.GitCommit=${git_commit}" \
-trimpath \
-o "$BUILD_DIR/pulse-sensor-proxy-$build_name" \
./cmd/pulse-sensor-proxy
# Create release archive with proper structure
tar_name="pulse-v${VERSION}-${build_name}.tar.gz"
# Create staging directory
staging_dir="$BUILD_DIR/staging-$build_name"
rm -rf "$staging_dir"
mkdir -p "$staging_dir/bin"
mkdir -p "$staging_dir/scripts"
# Copy binaries and VERSION file
cp "$BUILD_DIR/pulse-$build_name" "$staging_dir/bin/pulse"
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$staging_dir/bin/pulse-docker-agent"
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$staging_dir/bin/pulse-host-agent"
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$staging_dir/bin/pulse-sensor-proxy"
cp "scripts/install-docker-agent.sh" "$staging_dir/scripts/install-docker-agent.sh"
chmod 755 "$staging_dir/scripts/install-docker-agent.sh"
echo "$VERSION" > "$staging_dir/VERSION"
# Create tarball from staging directory
cd "$staging_dir"
tar -czf "../../$RELEASE_DIR/$tar_name" .
cd ../..
# Cleanup staging
rm -rf "$staging_dir"
echo "Created $RELEASE_DIR/$tar_name"
done
# Create universal tarball with all binaries
echo "Creating universal tarball..."
universal_dir="$BUILD_DIR/universal"
rm -rf "$universal_dir"
mkdir -p "$universal_dir/bin"
mkdir -p "$universal_dir/scripts"
# Copy all binaries to bin/ directory to maintain consistent structure
for build_name in "${!builds[@]}"; do
cp "$BUILD_DIR/pulse-$build_name" "$universal_dir/bin/pulse-${build_name}"
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$universal_dir/bin/pulse-docker-agent-${build_name}"
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$universal_dir/bin/pulse-host-agent-${build_name}"
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$universal_dir/bin/pulse-sensor-proxy-${build_name}"
done
cp "scripts/install-docker-agent.sh" "$universal_dir/scripts/install-docker-agent.sh"
chmod 755 "$universal_dir/scripts/install-docker-agent.sh"
# Create a detection script that creates the pulse symlink based on architecture
cat > "$universal_dir/bin/pulse" << 'EOF'
#!/bin/sh
# Auto-detect architecture and run appropriate binary
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64)
exec "$(dirname "$0")/pulse-linux-amd64" "$@"
;;
aarch64|arm64)
exec "$(dirname "$0")/pulse-linux-arm64" "$@"
;;
armv7l|armhf)
exec "$(dirname "$0")/pulse-linux-armv7" "$@"
;;
*)
echo "Unsupported architecture: $ARCH" >&2
exit 1
;;
esac
EOF
chmod +x "$universal_dir/bin/pulse"
cat > "$universal_dir/bin/pulse-docker-agent" << 'EOF'
#!/bin/sh
# Auto-detect architecture and run appropriate pulse-docker-agent binary
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64)
exec "$(dirname "$0")/pulse-docker-agent-linux-amd64" "$@"
;;
aarch64|arm64)
exec "$(dirname "$0")/pulse-docker-agent-linux-arm64" "$@"
;;
armv7l|armhf)
exec "$(dirname "$0")/pulse-docker-agent-linux-armv7" "$@"
;;
*)
echo "Unsupported architecture: $ARCH" >&2
exit 1
;;
esac
EOF
chmod +x "$universal_dir/bin/pulse-docker-agent"
cat > "$universal_dir/bin/pulse-sensor-proxy" << 'EOF'
#!/bin/sh
# Auto-detect architecture and run appropriate pulse-sensor-proxy binary
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64)
exec "$(dirname "$0")/pulse-sensor-proxy-linux-amd64" "$@"
;;
aarch64|arm64)
exec "$(dirname "$0")/pulse-sensor-proxy-linux-arm64" "$@"
;;
armv7l|armhf)
exec "$(dirname "$0")/pulse-sensor-proxy-linux-armv7" "$@"
;;
*)
echo "Unsupported architecture: $ARCH" >&2
exit 1
;;
esac
EOF
chmod +x "$universal_dir/bin/pulse-sensor-proxy"
cat > "$universal_dir/bin/pulse-host-agent" << 'EOF'
#!/bin/sh
# Auto-detect architecture and run appropriate pulse-host-agent binary
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64)
exec "$(dirname "$0")/pulse-host-agent-linux-amd64" "$@"
;;
aarch64|arm64)
exec "$(dirname "$0")/pulse-host-agent-linux-arm64" "$@"
;;
armv7l|armhf)
exec "$(dirname "$0")/pulse-host-agent-linux-armv7" "$@"
;;
*)
echo "Unsupported architecture: $ARCH" >&2
exit 1
;;
esac
EOF
chmod +x "$universal_dir/bin/pulse-host-agent"
# Add VERSION file
echo "$VERSION" > "$universal_dir/VERSION"
# Build host agent for macOS arm64
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}" \
-trimpath \
-o "$BUILD_DIR/pulse-host-agent-darwin-arm64" \
./cmd/pulse-host-agent
# Package macOS host agent
tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-arm64
# Create universal tarball
cd "$universal_dir"
tar -czf "../../$RELEASE_DIR/pulse-v${VERSION}.tar.gz" .
cd ../..
# Cleanup
rm -rf "$universal_dir"
# Copy standalone pulse-sensor-proxy binaries to release directory
# These are needed by install-sensor-proxy.sh installer script
echo "Copying standalone pulse-sensor-proxy binaries..."
for build_name in "${!builds[@]}"; do
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$RELEASE_DIR/"
done
# Optionally package Helm chart
if [ "${SKIP_HELM_PACKAGE:-0}" != "1" ]; then
if command -v helm >/dev/null 2>&1; then
echo "Packaging Helm chart..."
./scripts/package-helm-chart.sh "$VERSION"
if [ -f "dist/pulse-$VERSION.tgz" ]; then
cp "dist/pulse-$VERSION.tgz" "$RELEASE_DIR/"
fi
else
echo "Helm not found on PATH; skipping Helm chart packaging. Install Helm 3.9+ or set SKIP_HELM_PACKAGE=1 to silence this message."
fi
fi
# Generate checksums (include tarballs, helm chart, and standalone binaries)
cd "$RELEASE_DIR"
shopt -s nullglob
checksum_files=( *.tar.gz pulse-sensor-proxy-* )
if compgen -G "pulse-*.tgz" > /dev/null; then
checksum_files+=( pulse-*.tgz )
fi
if [ ${#checksum_files[@]} -eq 0 ]; then
echo "Warning: no release artifacts found to checksum."
else
# Generate individual .sha256 files for each asset (required by install.sh)
for file in "${checksum_files[@]}"; do
sha256sum "$file" | awk '{print $1}' > "${file}.sha256"
done
# Also generate combined checksums.txt for convenience
sha256sum "${checksum_files[@]}" > checksums.txt
if [ -n "${SIGNING_KEY_ID:-}" ]; then
if command -v gpg >/dev/null 2>&1; then
echo "Signing checksums with GPG key ${SIGNING_KEY_ID}..."
gpg --batch --yes --detach-sign --armor \
--local-user "${SIGNING_KEY_ID}" \
--output checksums.txt.asc \
checksums.txt
else
echo "SIGNING_KEY_ID is set but gpg is not installed; skipping signature."
fi
fi
fi
shopt -u nullglob
cd ..
echo
echo "Release build complete!"
echo "Archives created in $RELEASE_DIR/"
ls -lh $RELEASE_DIR/