name: Release on: release: types: [published] workflow_dispatch: inputs: tag: description: "Tag to release (e.g., v1.0.0)" required: true default: "v1.0.0" permissions: contents: write actions: read packages: write env: GO_VERSION_FILE: "go.mod" jobs: validate: name: Validate Release runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} is_prerelease: ${{ steps.version.outputs.is_prerelease }} steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Validate tag format id: version run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then TAG_NAME="${{ github.event.inputs.tag }}" else TAG_NAME="${GITHUB_REF#refs/tags/}" fi echo "Tag name: $TAG_NAME" # Validate semantic versioning format if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then echo "โŒ Invalid tag format: $TAG_NAME" echo "Expected format: v1.2.3 or v1.2.3-beta.1" exit 1 fi # Extract version without 'v' prefix VERSION=${TAG_NAME#v} echo "version=$VERSION" >> $GITHUB_OUTPUT # Check if prerelease (contains hyphen) if [[ "$TAG_NAME" =~ - ]]; then echo "is_prerelease=true" >> $GITHUB_OUTPUT echo "๐Ÿ“ฆ Prerelease detected: $TAG_NAME" else echo "is_prerelease=false" >> $GITHUB_OUTPUT echo "๐Ÿš€ Stable release detected: $TAG_NAME" fi - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: ${{ env.GO_VERSION_FILE }} - name: Install libpcap run: sudo apt-get install -y libpcap-dev - name: Run tests before release run: | echo "Running final tests before release..." go test -v ./... echo "โœ… All tests passed" build: name: Build Release Binaries runs-on: ubuntu-latest needs: validate strategy: matrix: include: - goos: linux goarch: amd64 - goos: linux goarch: arm64 - goos: linux goarch: arm goarm: 7 - goos: darwin goarch: amd64 - goos: darwin goarch: arm64 - goos: windows goarch: amd64 - goos: freebsd goarch: amd64 steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: ${{ env.GO_VERSION_FILE }} - name: Cache Go modules uses: actions/cache@v5 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}-${{ hashFiles('**/go.sum') }} - name: Build binaries env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: 0 run: | # Common variables ARCH_SUFFIX="${{ matrix.goos }}-${{ matrix.goarch }}" if [[ "${{ matrix.goarm }}" != "" ]]; then ARCH_SUFFIX="${ARCH_SUFFIX}v${{ matrix.goarm }}" fi # Function to build a binary build_binary() { local BINARY_NAME=$1 local CMD_PATH=$2 local OUTPUT_NAME # Ensure build directory exists mkdir -p build if [[ "${{ matrix.goos }}" == "windows" ]]; then OUTPUT_NAME="build/${BINARY_NAME}-v${{ needs.validate.outputs.version }}-${ARCH_SUFFIX}.exe" else OUTPUT_NAME="build/${BINARY_NAME}-v${{ needs.validate.outputs.version }}-${ARCH_SUFFIX}" fi echo "Building $BINARY_NAME: $OUTPUT_NAME" # Ensure clean build environment for this binary rm -f "$OUTPUT_NAME" "$OUTPUT_NAME.sha256" "$OUTPUT_NAME.sha512" if ! go build \ -ldflags="-s -w" \ -o "$OUTPUT_NAME" \ "$CMD_PATH"; then echo "โŒ Build failed for $BINARY_NAME" exit 1 fi # Verify binary was created ls -la "$OUTPUT_NAME" echo "$BINARY_NAME=$OUTPUT_NAME" >> $GITHUB_OUTPUT } # Build CLI build_binary "soundtouch-cli" "./cmd/soundtouch-cli" # Build Service build_binary "soundtouch-service" "./cmd/soundtouch-service" # Build Web build_binary "soundtouch-web" "./cmd/soundtouch-web" id: build - name: Generate individual checksums run: | CLI_NAME="${{ steps.build.outputs.soundtouch-cli }}" SVC_NAME="${{ steps.build.outputs.soundtouch-service }}" WEB_NAME="${{ steps.build.outputs.soundtouch-web }}" # Use atomic operations to avoid conflicts TEMP_DIR=$(mktemp -d) generate_checksums() { local FILE=$1 echo "Building checksums for: $FILE" sha256sum "$FILE" > "${TEMP_DIR}/$(basename "$FILE").sha256" sha512sum "$FILE" > "${TEMP_DIR}/$(basename "$FILE").sha512" mv "${TEMP_DIR}/$(basename "$FILE").sha256" "$FILE.sha256" mv "${TEMP_DIR}/$(basename "$FILE").sha512" "$FILE.sha512" } generate_checksums "$CLI_NAME" generate_checksums "$SVC_NAME" generate_checksums "$WEB_NAME" # Cleanup rm -rf "$TEMP_DIR" echo "โœ… Checksums generated successfully" - name: Upload build artifact uses: actions/upload-artifact@v7 with: name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm }} path: | build/soundtouch-cli-v* build/soundtouch-service-v* build/soundtouch-web-v* retention-days: 1 checksums: name: Generate Checksums runs-on: ubuntu-latest needs: [validate, build] steps: - name: Download binary artifacts uses: actions/download-artifact@v8 with: pattern: binaries-* path: ./binaries - name: Generate checksums run: | cd binaries # Debug: Show the downloaded structure echo "๐Ÿ“ Downloaded artifact structure:" ls -R # Create a collection directory to avoid naming conflicts mkdir -p release-files # Move all files from subdirectories to the collection directory find . -mindepth 2 -type f \( -name "soundtouch-cli-*" -o -name "soundtouch-service-*" -o -name "soundtouch-web-*" \) -exec mv {} release-files/ \; # Remove empty directories find . -type d -empty -delete # Move to the collection directory for the rest of the processing cd release-files # Debug: Show flattened structure echo "๐Ÿ“ Flattened structure:" ls -la soundtouch-* || echo "No files found matching pattern" # Generate combined checksums (exclude individual .sha256/.sha512 files) if ls soundtouch-* 1> /dev/null 2>&1; then # Only checksum the actual binaries, not the .sha256/.sha512 files ls soundtouch-* | grep -v '\.sha256$' | grep -v '\.sha512$' | xargs sha256sum > checksums.sha256 ls soundtouch-* | grep -v '\.sha256$' | grep -v '\.sha512$' | xargs sha512sum > checksums.sha512 echo "๐Ÿ“‹ Generated combined checksums:" cat checksums.sha256 # Verify all expected files are present (binaries only, not checksum files) EXPECTED_COUNT=21 # 7 platforms * 3 binaries ACTUAL_COUNT=$(ls soundtouch-* | grep -v '\.sha256$' | grep -v '\.sha512$' | wc -l) if [[ $ACTUAL_COUNT -ne $EXPECTED_COUNT ]]; then echo "โŒ Expected $EXPECTED_COUNT binaries, found $ACTUAL_COUNT" ls -la exit 1 fi echo "โœ… All $ACTUAL_COUNT binaries present" else echo "โŒ No binary files found" echo "Directory contents:" ls -la exit 1 fi - name: Upload checksums uses: actions/upload-artifact@v7 with: name: checksums path: | binaries/release-files/checksums.sha256 binaries/release-files/checksums.sha512 binaries/release-files/*.sha256 binaries/release-files/*.sha512 retention-days: 1 - name: Upload all release assets uses: actions/upload-artifact@v7 with: name: release-assets path: binaries/release-files/ retention-days: 1 create_release: name: Create GitHub Release runs-on: ubuntu-latest needs: [validate, checksums] if: github.event_name == 'workflow_dispatch' steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Download release assets uses: actions/download-artifact@v8 with: name: release-assets path: ./release-assets - name: Generate release notes id: release_notes run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then TAG_NAME="${{ github.event.inputs.tag }}" else TAG_NAME="${{ github.event.release.tag_name }}" fi VERSION="${TAG_NAME#v}" # Generate comprehensive release notes cat > release_notes.md << EOF # Bose SoundTouch Go Library $TAG_NAME A comprehensive Go library for controlling Bose SoundTouch speakers with 100% API coverage, real-time WebSocket events, and production-ready features. ## ๐ŸŽฏ Key Features - **100% API Coverage**: All 19 official endpoints + 6 useful extensions (25 total) - **Real-time Events**: WebSocket support with auto-reconnect and comprehensive event handling - **Multiroom Control**: Complete zone management and coordination - **Production Ready**: Connection pooling, error handling, circuit breakers, monitoring - **Excellent Documentation**: 4000+ lines including Getting Started, Cookbook, Troubleshooting, and Deployment guides - **CLI Tool**: Full-featured command-line interface with all endpoints ## ๐Ÿš€ Quick Start \`\`\`bash go get github.com/gesellix/bose-soundtouch@$TAG_NAME \`\`\` \`\`\`go package main import ( "fmt" "log" "github.com/gesellix/bose-soundtouch/pkg/client" ) func main() { // Create client c := client.New("192.168.1.100", 8090) // Get device info info, err := c.GetInfo() if err != nil { log.Fatal(err) } fmt.Printf("Device: %s\\n", info.Name) } \`\`\` ## ๐Ÿ“š Documentation - [Getting Started Guide](docs/GETTING-STARTED.md) - 10-minute tutorial from discovery to WebSocket monitoring - [API Cookbook](docs/API-COOKBOOK.md) - 1000+ lines of real-world patterns and examples - [Troubleshooting Guide](docs/TROUBLESHOOTING.md) - Systematic issue resolution - [Deployment Guide](docs/DEPLOYMENT.md) - Production deployment examples (Docker, K8s, systemd) ## ๐Ÿ”ง CLI & Service Tools Download the tools for your platform from the assets below: ### CLI Tool \`\`\`bash # Quick device discovery ./soundtouch-cli -discover \`\`\` ### SoundTouch Service \`\`\`bash # Start the service ./soundtouch-service \`\`\` ### SoundTouch Web \`\`\`bash # Start the web app ./soundtouch-web \`\`\` ## ๐Ÿงช Tested Hardware - Bose SoundTouch 10 - Bose SoundTouch 20 - All core functionality validated on real devices ## ๐Ÿ“ˆ What's New in $TAG_NAME $(git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD 2>/dev/null || echo "- Initial release with complete feature set") ## ๐Ÿ—๏ธ Supported Platforms This release includes pre-built binaries for: - Linux (amd64, arm64, armv7) - macOS (Intel & Apple Silicon) - Windows (amd64) - FreeBSD (amd64) `soundtouch-cli`, `soundtouch-service`, and `soundtouch-web` are included. ## ๐Ÿ” Checksums Multiple checksum options are provided for download verification: ### Combined Checksums (Recommended) - \`checksums.sha256\` - SHA256 checksums for all binaries - \`checksums.sha512\` - SHA512 checksums for all binaries \`\`\`bash # Download any binary + combined checksums curl -L -O https://github.com/.../soundtouch-cli-v$TAG_NAME-linux-amd64 curl -L -O https://github.com/.../checksums.sha256 # Verify your specific download sha256sum -c checksums.sha256 --ignore-missing \`\`\` ### Individual Checksums (Per Binary) Each binary also has its own dedicated checksum files: - \`soundtouch-cli-v$TAG_NAME-platform.sha256\` - \`soundtouch-cli-v$TAG_NAME-platform.sha512\` \`\`\`bash # Download binary + its individual checksum curl -L -O https://github.com/.../soundtouch-cli-v$TAG_NAME-linux-amd64 curl -L -O https://github.com/.../soundtouch-cli-v$TAG_NAME-linux-amd64.sha256 # Verify with individual checksum sha256sum -c soundtouch-cli-v$TAG_NAME-linux-amd64.sha256 \`\`\` ## ๐Ÿค Contributing Contributions welcome! See our documentation for examples and patterns. ## ๐Ÿ“„ License MIT License - see [LICENSE](LICENSE) file. EOF echo "release_notes_file=release_notes.md" >> $GITHUB_OUTPUT - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.inputs.tag }} name: "Bose SoundTouch Go Library ${{ github.event.inputs.tag }}" body_path: ${{ steps.release_notes.outputs.release_notes_file }} draft: false prerelease: ${{ needs.validate.outputs.is_prerelease == 'true' }} files: | release-assets/soundtouch-cli-v* release-assets/soundtouch-service-v* release-assets/soundtouch-web-v* release-assets/checksums.sha256 release-assets/checksums.sha512 fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} update_release: name: Update Existing Release runs-on: ubuntu-latest needs: [validate, checksums] if: github.event_name == 'release' && github.event.action == 'published' steps: - name: Download release assets uses: actions/download-artifact@v8 with: name: release-assets path: ./release-assets - name: Upload additional assets to existing release uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.release.tag_name }} files: | release-assets/soundtouch-cli-v* release-assets/soundtouch-service-v* release-assets/soundtouch-web-v* release-assets/checksums.sha256 release-assets/checksums.sha512 fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} docker: name: Build and Push Docker Image runs-on: ubuntu-latest needs: validate steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v6 with: images: ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}},value=v${{ needs.validate.outputs.version }} type=semver,pattern={{major}}.{{minor}},value=v${{ needs.validate.outputs.version }} type=raw,value=latest,enable=${{ needs.validate.outputs.is_prerelease == 'false' }} - name: Build and push Docker image uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/arm64/v8,linux/arm/v7 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max notify: name: Post-Release Notifications runs-on: ubuntu-latest needs: [validate, create_release, update_release, docker] if: always() && (needs.create_release.result == 'success' || needs.update_release.result == 'success' || needs.docker.result == 'success') steps: - name: Notify success run: | echo "๐ŸŽ‰ Release ${{ needs.validate.outputs.version }} completed successfully!" echo "๐Ÿ“ฆ Binaries built for 7 platforms (CLI, Service, and Web)" echo "๐Ÿณ Docker image published to ghcr.io" echo "๐Ÿ” Checksums generated and verified" echo "๐Ÿ“‹ Release notes automatically generated" echo "" TAG_NAME="${{ github.event.inputs.tag || github.event.release.tag_name }}" echo "๐Ÿ”— Release URL: https://github.com/${{ github.repository }}/releases/tag/${TAG_NAME}" echo "" echo "Next steps:" echo "- Monitor download metrics" echo "- Update documentation if needed" echo "- Announce to community (see scripts/post-release.md)"