diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..0d928785b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,150 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version number (e.g., 4.28.1)' + required: true + type: string + +jobs: + create-release: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + # Install zip for Windows binaries + sudo apt-get update + sudo apt-get install -y zip + + # Install Helm for chart packaging + curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + + - name: Build release artifacts + run: | + echo "Building release v${{ inputs.version }}..." + ./scripts/build-release.sh ${{ inputs.version }} + + - name: Validate release artifacts + run: | + echo "Validating release v${{ inputs.version }}..." + # Note: Docker image validation is skipped in this workflow + # since we're only validating the built artifacts (tarballs, binaries, checksums) + # Docker images should be validated separately before release + + # Run validation with --skip-docker flag + ./scripts/validate-release.sh ${{ inputs.version }} --skip-docker || { + echo "❌ Release validation failed!" + echo "The workflow will now fail. No release will be created." + exit 1 + } + + - name: Create draft release + id: create_release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${{ inputs.version }}" + TAG="v${VERSION}" + + echo "Creating draft release for ${TAG}..." + + # Create draft release with placeholder body + gh release create "${TAG}" \ + --draft \ + --title "Pulse ${TAG}" \ + --notes "Release ${TAG} + + ## What's Changed + + + + ## Installation + + Quick install on Linux: + \`\`\`bash + curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/main/install.sh | sudo bash + \`\`\` + + Or download platform-specific archives below." + + echo "release_url=$(gh release view ${TAG} --json url -q .url)" >> $GITHUB_OUTPUT + echo "tag=${TAG}" >> $GITHUB_OUTPUT + + - name: Upload checksums.txt first + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${{ steps.create_release.outputs.tag }}" + + echo "Uploading checksums.txt..." + gh release upload "${TAG}" release/checksums.txt + + # Also upload individual .sha256 files + echo "Uploading individual checksum files..." + gh release upload "${TAG}" release/*.sha256 + + - name: Upload release assets + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${{ steps.create_release.outputs.tag }}" + + echo "Uploading release assets..." + + # Upload tarballs + gh release upload "${TAG}" release/*.tar.gz + + # Upload Windows zip files + gh release upload "${TAG}" release/*.zip + + # Upload Helm chart if it exists + if ls release/*.tgz 1> /dev/null 2>&1; then + echo "Uploading Helm chart..." + gh release upload "${TAG}" release/*.tgz + fi + + # Upload install.sh + gh release upload "${TAG}" release/install.sh + + # Upload standalone binaries (for direct download by installers) + echo "Uploading standalone binaries..." + gh release upload "${TAG}" release/pulse-sensor-proxy-* + gh release upload "${TAG}" release/pulse-host-agent-* + + - name: Output release information + run: | + echo "✅ Release draft created successfully!" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📦 Release: ${{ steps.create_release.outputs.tag }}" + echo "🔗 URL: ${{ steps.create_release.outputs.release_url }}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "⚠️ IMPORTANT: This release is in DRAFT status" + echo "" + echo "Next steps:" + echo "1. Review the release at the URL above" + echo "2. Update the release notes with changes since last release" + echo "3. Publish the release manually when ready" + echo "" + echo "All artifacts have been uploaded and validated." + echo "checksums.txt matches all uploaded binaries." + echo "" diff --git a/scripts/validate-release.sh b/scripts/validate-release.sh index 12cb90060..28575c8d1 100755 --- a/scripts/validate-release.sh +++ b/scripts/validate-release.sh @@ -3,9 +3,10 @@ # Pulse Release Validation Script # Comprehensive artifact validation to prevent missing files/binaries in releases # -# Usage: ./scripts/validate-release.sh [image] [release-dir] +# Usage: ./scripts/validate-release.sh [image] [release-dir] [--skip-docker] # Example: ./scripts/validate-release.sh 4.26.2 # ./scripts/validate-release.sh 4.26.2 rcourtman/pulse:v4.26.2 release +# ./scripts/validate-release.sh 4.26.2 --skip-docker set -euo pipefail @@ -33,17 +34,41 @@ warn() { } if [ $# -lt 1 ]; then - error "Usage: $0 [image] [release-dir]" + error "Usage: $0 [image] [release-dir] [--skip-docker]" exit 1 fi PULSE_VERSION=$1 PULSE_TAG="v${PULSE_VERSION}" -IMAGE=${2:-"rcourtman/pulse:${PULSE_TAG}"} -RELEASE_DIR=${3:-"release"} +SKIP_DOCKER=false + +# Parse arguments +shift +while [ $# -gt 0 ]; do + case "$1" in + --skip-docker) + SKIP_DOCKER=true + shift + ;; + *) + if [ -z "${IMAGE:-}" ]; then + IMAGE="$1" + elif [ -z "${RELEASE_DIR:-}" ]; then + RELEASE_DIR="$1" + fi + shift + ;; + esac +done + +# Set defaults +IMAGE=${IMAGE:-"rcourtman/pulse:${PULSE_TAG}"} +RELEASE_DIR=${RELEASE_DIR:-"release"} # Validate prerequisites -command -v docker >/dev/null || { error "docker is required"; exit 1; } +if [ "$SKIP_DOCKER" = false ]; then + command -v docker >/dev/null || { error "docker is required (use --skip-docker to skip Docker validation)"; exit 1; } +fi [ -d "$RELEASE_DIR" ] || { error "release dir not found: $RELEASE_DIR"; exit 1; } # Create temp directory for extractions @@ -58,43 +83,48 @@ echo "" #============================================================================= # DOCKER IMAGE VALIDATION #============================================================================= -info "=== Docker Image Validation ===" +if [ "$SKIP_DOCKER" = false ]; then + info "=== Docker Image Validation ===" -# Validate VERSION file in container -info "Checking VERSION file in Docker image..." -docker run --rm --entrypoint /bin/sh -e EXPECTED_VERSION="$PULSE_VERSION" "$IMAGE" -c 'set -euo pipefail; actual=$(cat /VERSION | tr -d "\r\n"); [ "$actual" = "$EXPECTED_VERSION" ] || { echo "VERSION mismatch: expected=$EXPECTED_VERSION actual=$actual" >&2; exit 1; }' || { error "VERSION file mismatch in Docker image"; exit 1; } -success "VERSION file correct: $PULSE_VERSION" + # Validate VERSION file in container + info "Checking VERSION file in Docker image..." + docker run --rm --entrypoint /bin/sh -e EXPECTED_VERSION="$PULSE_VERSION" "$IMAGE" -c 'set -euo pipefail; actual=$(cat /VERSION | tr -d "\r\n"); [ "$actual" = "$EXPECTED_VERSION" ] || { echo "VERSION mismatch: expected=$EXPECTED_VERSION actual=$actual" >&2; exit 1; }' || { error "VERSION file mismatch in Docker image"; exit 1; } + success "VERSION file correct: $PULSE_VERSION" -# Validate all required scripts exist and are executable -info "Checking installer/uninstaller scripts in /opt/pulse/scripts/..." -docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/scripts; required="install-docker-agent.sh install-container-agent.sh install-host-agent.sh install-host-agent.ps1 uninstall-host-agent.sh uninstall-host-agent.ps1 install-sensor-proxy.sh install-docker.sh"; for f in $required; do [ -f "$f" ] || { echo "missing script $f" >&2; exit 1; }; case "$f" in *.sh|*.ps1) [ -x "$f" ] || { echo "$f not executable" >&2; exit 1; };; esac; done; echo "All scripts present and executable"' || { error "Script validation failed"; exit 1; } -success "All installer/uninstaller scripts present and executable" + # Validate all required scripts exist and are executable + info "Checking installer/uninstaller scripts in /opt/pulse/scripts/..." + docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/scripts; required="install-docker-agent.sh install-container-agent.sh install-host-agent.sh install-host-agent.ps1 uninstall-host-agent.sh uninstall-host-agent.ps1 install-sensor-proxy.sh install-docker.sh"; for f in $required; do [ -f "$f" ] || { echo "missing script $f" >&2; exit 1; }; case "$f" in *.sh|*.ps1) [ -x "$f" ] || { echo "$f not executable" >&2; exit 1; };; esac; done; echo "All scripts present and executable"' || { error "Script validation failed"; exit 1; } + 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-docker-agent-linux-armv6 pulse-docker-agent-linux-386 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-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 pulse-sensor-proxy-linux-armv6 pulse-sensor-proxy-linux-386"; 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 (24 binaries + 2 Windows symlinks)" + # 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-docker-agent-linux-armv6 pulse-docker-agent-linux-386 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-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 pulse-sensor-proxy-linux-armv6 pulse-sensor-proxy-linux-386"; 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 (24 binaries + 2 Windows symlinks)" -# Validate version embedding in Docker image binaries -info "Validating version embedding in Docker image binaries..." + # Validate version embedding in Docker image binaries + info "Validating version embedding in Docker image binaries..." -# Pulse server binary -docker run --rm --entrypoint /app/pulse "$IMAGE" version 2>/dev/null | grep -Fx "Pulse $PULSE_TAG" >/dev/null || { error "Pulse server version mismatch"; exit 1; } -success "Pulse server version: $PULSE_TAG" + # Pulse server binary + docker run --rm --entrypoint /app/pulse "$IMAGE" version 2>/dev/null | grep -Fx "Pulse $PULSE_TAG" >/dev/null || { error "Pulse server version mismatch"; exit 1; } + success "Pulse server version: $PULSE_TAG" -# Host agent binary -docker run --rm --entrypoint /opt/pulse/bin/pulse-host-agent-linux-amd64 "$IMAGE" --version 2>/dev/null | grep -Fx "$PULSE_TAG" >/dev/null || { error "Host agent version mismatch"; exit 1; } -success "Host agent version: $PULSE_TAG" + # Host agent binary + docker run --rm --entrypoint /opt/pulse/bin/pulse-host-agent-linux-amd64 "$IMAGE" --version 2>/dev/null | grep -Fx "$PULSE_TAG" >/dev/null || { error "Host agent version mismatch"; exit 1; } + success "Host agent version: $PULSE_TAG" -# Sensor proxy binary -docker run --rm --entrypoint /opt/pulse/bin/pulse-sensor-proxy-linux-amd64 "$IMAGE" version 2>/dev/null | grep -Fx "pulse-sensor-proxy $PULSE_TAG" >/dev/null || { error "Sensor proxy version mismatch"; exit 1; } -success "Sensor proxy version: $PULSE_TAG" + # Sensor proxy binary + docker run --rm --entrypoint /opt/pulse/bin/pulse-sensor-proxy-linux-amd64 "$IMAGE" version 2>/dev/null | grep -Fx "pulse-sensor-proxy $PULSE_TAG" >/dev/null || { error "Sensor proxy version mismatch"; exit 1; } + success "Sensor proxy version: $PULSE_TAG" -# Docker agent binary (no CLI flag, check binary strings) -docker run --rm --entrypoint /bin/sh -e EXPECTED_TAG="$PULSE_TAG" "$IMAGE" -c 'set -euo pipefail; grep -aF "$EXPECTED_TAG" /opt/pulse/bin/pulse-docker-agent-linux-amd64 >/dev/null' || { error "Docker agent version string not found"; exit 1; } -success "Docker agent version embedded: $PULSE_TAG" + # Docker agent binary (no CLI flag, check binary strings) + docker run --rm --entrypoint /bin/sh -e EXPECTED_TAG="$PULSE_TAG" "$IMAGE" -c 'set -euo pipefail; grep -aF "$EXPECTED_TAG" /opt/pulse/bin/pulse-docker-agent-linux-amd64 >/dev/null' || { error "Docker agent version string not found"; exit 1; } + success "Docker agent version embedded: $PULSE_TAG" -echo "" + echo "" +else + warn "=== Skipping Docker Image Validation (--skip-docker flag provided) ===" + echo "" +fi #============================================================================= # RELEASE TARBALL VALIDATION