#!/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}..." # Require public key embedding for release-grade license validation. # Explicitly opt out with PULSE_ALLOW_MISSING_LICENSE_KEY=true (not recommended). LICENSE_LDFLAGS="" if [[ -z "${PULSE_LICENSE_PUBLIC_KEY:-}" ]]; then if [[ "${PULSE_ALLOW_MISSING_LICENSE_KEY:-false}" == "true" ]]; then echo "Warning: PULSE_LICENSE_PUBLIC_KEY not set; continuing because PULSE_ALLOW_MISSING_LICENSE_KEY=true." else echo "Error: PULSE_LICENSE_PUBLIC_KEY is required for release builds." >&2 echo "Set PULSE_ALLOW_MISSING_LICENSE_KEY=true only for local non-release debugging." >&2 exit 1 fi else decoded_key_len=$(printf '%s' "${PULSE_LICENSE_PUBLIC_KEY}" | openssl base64 -d -A 2>/dev/null | wc -c | tr -d ' ') if [[ "${decoded_key_len}" != "32" ]]; then echo "Error: PULSE_LICENSE_PUBLIC_KEY must decode to 32 bytes (Ed25519 public key)." >&2 exit 1 fi if [[ -n "${PULSE_LICENSE_PUBLIC_KEY_FINGERPRINT:-}" ]]; then expected_fingerprint="${PULSE_LICENSE_PUBLIC_KEY_FINGERPRINT#SHA256:}" actual_fingerprint=$(printf '%s' "${PULSE_LICENSE_PUBLIC_KEY}" | openssl base64 -d -A 2>/dev/null | openssl dgst -sha256 -binary | openssl base64 -A) if [[ -z "${actual_fingerprint}" ]]; then echo "Error: Failed to compute fingerprint for PULSE_LICENSE_PUBLIC_KEY." >&2 exit 1 fi if [[ "${actual_fingerprint}" != "${expected_fingerprint}" ]]; then echo "Error: PULSE_LICENSE_PUBLIC_KEY fingerprint mismatch." >&2 echo "Expected: SHA256:${expected_fingerprint}" >&2 echo "Actual: SHA256:${actual_fingerprint}" >&2 exit 1 fi echo "Verified license public key fingerprint: SHA256:${actual_fingerprint}" fi LICENSE_LDFLAGS="-X github.com/rcourtman/pulse-go-rewrite/internal/license.EmbeddedPublicKey=${PULSE_LICENSE_PUBLIC_KEY}" fi # 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 host agents for every supported platform/architecture so download endpoints work offline echo "Building host agents for all platforms..." host_agent_order=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386 darwin-amd64 darwin-arm64 freebsd-amd64 freebsd-arm64 windows-amd64 windows-arm64 windows-386) host_agent_envs=( "GOOS=linux GOARCH=amd64" "GOOS=linux GOARCH=arm64" "GOOS=linux GOARCH=arm GOARM=7" "GOOS=linux GOARCH=arm GOARM=6" "GOOS=linux GOARCH=386" "GOOS=darwin GOARCH=amd64" "GOOS=darwin GOARCH=arm64" "GOOS=freebsd GOARCH=amd64" "GOOS=freebsd GOARCH=arm64" "GOOS=windows GOARCH=amd64" "GOOS=windows GOARCH=arm64" "GOOS=windows GOARCH=386" ) if [[ ${#host_agent_order[@]} -ne ${#host_agent_envs[@]} ]]; then echo "Host agent build config mismatch." >&2 exit 1 fi for i in "${!host_agent_order[@]}"; do target="${host_agent_order[$i]}" build_env="${host_agent_envs[$i]}" output_path="$BUILD_DIR/pulse-host-agent-$target" if [[ "$target" == windows-* ]]; then output_path="${output_path}.exe" fi env $build_env go build \ -ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/hostagent.Version=v${VERSION}" \ -trimpath \ -o "$output_path" \ ./cmd/pulse-host-agent done # Build unified agents for every supported platform/architecture echo "Building unified agents for all platforms..." for i in "${!host_agent_order[@]}"; do target="${host_agent_order[$i]}" build_env="${host_agent_envs[$i]}" output_path="$BUILD_DIR/pulse-agent-$target" if [[ "$target" == windows-* ]]; then output_path="${output_path}.exe" fi env $build_env go build \ -ldflags="-s -w -X main.Version=v${VERSION}" \ -trimpath \ -o "$output_path" \ ./cmd/pulse-agent done # Build for different architectures (server + agents) build_order=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386) build_envs=( "GOOS=linux GOARCH=amd64" "GOOS=linux GOARCH=arm64" "GOOS=linux GOARCH=arm GOARM=7" "GOOS=linux GOARCH=arm GOARM=6" "GOOS=linux GOARCH=386" ) if [[ ${#build_order[@]} -ne ${#build_envs[@]} ]]; then echo "Build target config mismatch." >&2 exit 1 fi for i in "${!build_order[@]}"; do build_name="${build_order[$i]}" echo "Building for $build_name..." build_env="${build_envs[$i]}" 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} ${LICENSE_LDFLAGS}" \ -trimpath \ -o "$BUILD_DIR/pulse-$build_name" \ ./cmd/pulse done # Create platform-specific tarballs that include all host agent binaries for download endpoints for build_name in "${build_order[@]}"; do echo "Packaging release for $build_name..." tar_name="pulse-v${VERSION}-${build_name}.tar.gz" staging_dir="$BUILD_DIR/staging-$build_name" rm -rf "$staging_dir" mkdir -p "$staging_dir/bin" mkdir -p "$staging_dir/scripts" # Copy architecture-specific runtime binaries cp "$BUILD_DIR/pulse-$build_name" "$staging_dir/bin/pulse" cp "$BUILD_DIR/pulse-host-agent-$build_name" "$staging_dir/bin/pulse-host-agent" # Copy host agent binaries for every supported platform/architecture for target in "${host_agent_order[@]}"; do src="$BUILD_DIR/pulse-host-agent-$target" dest="$staging_dir/bin/pulse-host-agent-$target" if [[ "$target" == windows-* ]]; then src="${src}.exe" dest="${dest}.exe" fi cp "$src" "$dest" done ( cd "$staging_dir/bin" && ln -sf pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 && ln -sf pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 && ln -sf pulse-host-agent-windows-386.exe pulse-host-agent-windows-386 ) # Copy unified agent binaries for every supported platform/architecture for target in "${host_agent_order[@]}"; do src="$BUILD_DIR/pulse-agent-$target" dest="$staging_dir/bin/pulse-agent-$target" if [[ "$target" == windows-* ]]; then src="${src}.exe" dest="${dest}.exe" fi cp "$src" "$dest" done ( cd "$staging_dir/bin" && ln -sf pulse-agent-windows-amd64.exe pulse-agent-windows-amd64 && ln -sf pulse-agent-windows-arm64.exe pulse-agent-windows-arm64 && ln -sf pulse-agent-windows-386.exe pulse-agent-windows-386 ) # Copy scripts and VERSION metadata cp "scripts/install-docker-agent.sh" "$staging_dir/scripts/install-docker-agent.sh" cp "scripts/install-container-agent.sh" "$staging_dir/scripts/install-container-agent.sh" cp "scripts/install-host-agent.ps1" "$staging_dir/scripts/install-host-agent.ps1" cp "scripts/uninstall-host-agent.sh" "$staging_dir/scripts/uninstall-host-agent.sh" cp "scripts/uninstall-host-agent.ps1" "$staging_dir/scripts/uninstall-host-agent.ps1" cp "scripts/install-docker.sh" "$staging_dir/scripts/install-docker.sh" cp "scripts/install.sh" "$staging_dir/scripts/install.sh" [ -f "scripts/install.ps1" ] && cp "scripts/install.ps1" "$staging_dir/scripts/install.ps1" chmod 755 "$staging_dir/scripts/"*.sh chmod 755 "$staging_dir/scripts/"*.ps1 2>/dev/null || true echo "$VERSION" > "$staging_dir/VERSION" # Create tarball from staging directory cd "$staging_dir" tar -czf "../../$RELEASE_DIR/$tar_name" . cd ../.. 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 "${build_order[@]}"; do cp "$BUILD_DIR/pulse-$build_name" "$universal_dir/bin/pulse-${build_name}" cp "$BUILD_DIR/pulse-host-agent-$build_name" "$universal_dir/bin/pulse-host-agent-${build_name}" cp "$BUILD_DIR/pulse-agent-$build_name" "$universal_dir/bin/pulse-agent-${build_name}" done cp "scripts/install-docker-agent.sh" "$universal_dir/scripts/install-docker-agent.sh" cp "scripts/install-container-agent.sh" "$universal_dir/scripts/install-container-agent.sh" cp "scripts/install-host-agent.ps1" "$universal_dir/scripts/install-host-agent.ps1" cp "scripts/uninstall-host-agent.sh" "$universal_dir/scripts/uninstall-host-agent.sh" cp "scripts/uninstall-host-agent.ps1" "$universal_dir/scripts/uninstall-host-agent.ps1" cp "scripts/install-docker.sh" "$universal_dir/scripts/install-docker.sh" cp "scripts/install.sh" "$universal_dir/scripts/install.sh" [ -f "scripts/install.ps1" ] && cp "scripts/install.ps1" "$universal_dir/scripts/install.ps1" chmod 755 "$universal_dir/scripts/"*.sh chmod 755 "$universal_dir/scripts/"*.ps1 2>/dev/null || true # 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-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" cat > "$universal_dir/bin/pulse-agent" << 'EOF' #!/bin/sh # Auto-detect architecture and run appropriate pulse-agent binary ARCH=$(uname -m) case "$ARCH" in x86_64|amd64) exec "$(dirname "$0")/pulse-agent-linux-amd64" "$@" ;; aarch64|arm64) exec "$(dirname "$0")/pulse-agent-linux-arm64" "$@" ;; armv7l|armhf) exec "$(dirname "$0")/pulse-agent-linux-armv7" "$@" ;; *) echo "Unsupported architecture: $ARCH" >&2 exit 1 ;; esac EOF chmod +x "$universal_dir/bin/pulse-agent" # Add VERSION file echo "$VERSION" > "$universal_dir/VERSION" # Package standalone host agent binaries (all platforms) # Linux tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-amd64 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-arm64 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-armv7.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-armv7 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-armv6.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-armv6 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-386 # Darwin 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 # FreeBSD tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-freebsd-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-freebsd-amd64 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-freebsd-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-freebsd-arm64 # Windows 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" zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-host-agent-windows-386.exe" # Package standalone unified agent binaries (all platforms) # Linux tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-amd64 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-arm64 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-armv7.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-armv7 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-armv6.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-armv6 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-386 # Darwin tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-amd64 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-arm64 # FreeBSD tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-freebsd-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-freebsd-amd64 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-freebsd-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-freebsd-arm64 # Windows (zip archives with version in filename) zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR/pulse-agent-windows-amd64.exe" zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-agent-windows-arm64.exe" zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-agent-windows-386.exe" # Also copy bare binaries for /releases/latest/download/ redirect compatibility # These allow LXC/barebone installs to redirect to GitHub without needing versioned URLs echo "Copying bare binaries to release directory for redirect compatibility..." cp "$BUILD_DIR/pulse-agent-windows-amd64.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-agent-windows-arm64.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-agent-windows-386.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-host-agent-windows-386.exe" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-agent-freebsd-amd64" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-agent-freebsd-arm64" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-host-agent-freebsd-amd64" "$RELEASE_DIR/" cp "$BUILD_DIR/pulse-host-agent-freebsd-arm64" "$RELEASE_DIR/" # Copy Windows, macOS, and FreeBSD binaries into universal tarball for /download/ endpoint echo "Adding Windows, macOS, and FreeBSD binaries to universal tarball..." cp "$BUILD_DIR/pulse-host-agent-darwin-amd64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-darwin-arm64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-freebsd-amd64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-freebsd-arm64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-host-agent-windows-386.exe" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-darwin-amd64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-darwin-arm64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-freebsd-amd64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-freebsd-arm64" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-windows-amd64.exe" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-windows-arm64.exe" "$universal_dir/bin/" cp "$BUILD_DIR/pulse-agent-windows-386.exe" "$universal_dir/bin/" # Create symlinks for Windows binaries without .exe extension (required for download endpoint) ln -s pulse-host-agent-windows-amd64.exe "$universal_dir/bin/pulse-host-agent-windows-amd64" ln -s pulse-host-agent-windows-arm64.exe "$universal_dir/bin/pulse-host-agent-windows-arm64" ln -s pulse-host-agent-windows-386.exe "$universal_dir/bin/pulse-host-agent-windows-386" ln -s pulse-agent-windows-amd64.exe "$universal_dir/bin/pulse-agent-windows-amd64" ln -s pulse-agent-windows-arm64.exe "$universal_dir/bin/pulse-agent-windows-arm64" ln -s pulse-agent-windows-386.exe "$universal_dir/bin/pulse-agent-windows-386" # Create universal tarball cd "$universal_dir" tar -czf "../../$RELEASE_DIR/pulse-v${VERSION}.tar.gz" . cd ../.. # Cleanup rm -rf "$universal_dir" # 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 # Copy install scripts to release directory (required for GitHub releases) # These are uploaded as standalone assets so users can: # curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash # instead of pulling from main branch (which may have newer, incompatible changes) echo "Copying install scripts to release directory..." cp install.sh "$RELEASE_DIR/" cp scripts/install-docker.sh "$RELEASE_DIR/" cp scripts/pulse-auto-update.sh "$RELEASE_DIR/" # Generate checksums (include tarballs, zip files, helm chart, and install.sh) cd "$RELEASE_DIR" shopt -s nullglob extglob checksum_files=() # Match all tarballs, zip files, exe files, and install scripts if compgen -G "pulse-*.tar.gz" > /dev/null; then checksum_files+=( pulse-*.tar.gz ) fi if compgen -G "pulse-*.tgz" > /dev/null; then checksum_files+=( pulse-*.tgz ) fi if compgen -G "pulse-*.zip" > /dev/null; then checksum_files+=( pulse-*.zip ) fi if compgen -G "pulse-*.exe" > /dev/null; then checksum_files+=( pulse-*.exe ) fi if [ -f "install.sh" ]; then checksum_files+=( install.sh ) fi if [ -f "install-docker.sh" ]; then checksum_files+=( install-docker.sh ) fi if [ -f "pulse-auto-update.sh" ]; then checksum_files+=( pulse-auto-update.sh ) fi if compgen -G "install*.ps1" > /dev/null; then checksum_files+=( install*.ps1 ) fi if [ ${#checksum_files[@]} -eq 0 ]; then echo "Warning: no release artifacts found to checksum." else # Generate checksums from a single sha256sum run for deterministic results (prevents #671 checksum mismatches) checksum_output="$(sha256sum "${checksum_files[@]}" | sort -k 2)" printf '%s\n' "$checksum_output" > checksums.txt # Emit per-file .sha256 artifacts for backward compatibility while legacy installers transition off them while read -r checksum filename; do printf '%s %s\n' "$checksum" "$filename" > "${filename}.sha256" done <<< "$checksum_output" 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/ # Create host-agent manifest (per tarball) for validation/debugging manifest_path="$RELEASE_DIR/host-agent-manifest.json" echo "Generating host-agent manifest at $manifest_path..." python3 - <<'EOF' "$RELEASE_DIR" "$VERSION" "$manifest_path" import json import os import sys import tarfile release_dir = sys.argv[1] version = sys.argv[2] manifest_path = sys.argv[3] tar_arches = [ "linux-amd64", "linux-arm64", "linux-armv7", "linux-armv6", "linux-386", ] host_agents = [ "pulse-host-agent-linux-amd64", "pulse-host-agent-linux-arm64", "pulse-host-agent-linux-armv7", "pulse-host-agent-linux-armv6", "pulse-host-agent-linux-386", "pulse-host-agent-darwin-amd64", "pulse-host-agent-darwin-arm64", "pulse-host-agent-windows-amd64.exe", "pulse-host-agent-windows-arm64.exe", "pulse-host-agent-windows-386.exe", "pulse-host-agent-windows-amd64", "pulse-host-agent-windows-arm64", "pulse-host-agent-windows-386", "pulse-host-agent-freebsd-amd64", "pulse-host-agent-freebsd-arm64", ] manifest = { "version": version, "tarballs": {}, "universal": [], } def collect_agents(tar_path): found = [] try: with tarfile.open(tar_path, "r:gz") as tf: names = set(m.name for m in tf.getmembers() if (m.isfile() or m.issym())) for agent in host_agents: target = f"./bin/{agent}" if target in names: found.append(agent) except Exception as exc: print(f"Failed to read {tar_path}: {exc}", file=sys.stderr) return sorted(found) # Platform tarballs for arch in tar_arches: tarball = os.path.join(release_dir, f"pulse-v{version}-{arch}.tar.gz") if os.path.exists(tarball): manifest["tarballs"][arch] = collect_agents(tarball) # Universal tarball universal_tar = os.path.join(release_dir, f"pulse-v{version}.tar.gz") if os.path.exists(universal_tar): manifest["universal"] = collect_agents(universal_tar) with open(manifest_path, "w", encoding="utf-8") as handle: json.dump(manifest, handle, indent=2) print(json.dumps(manifest, indent=2)) EOF