Release #68
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: ["v*"] | |
| # Manual re-publish of the Docker image for an existing release, without | |
| # rebuilding binaries or cutting a new version. Runs only the docker (+ | |
| # homebrew) jobs against the given tag's already-published release assets. | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Existing release tag to (re)build + push the Docker image for, e.g. v0.6.9" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| build: | |
| # Binaries are only built when a tag is pushed. A manual dispatch reuses | |
| # the existing release's binaries, so it skips this job entirely. | |
| if: github.event_name == 'push' | |
| permissions: | |
| contents: read | |
| name: Build ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-apple-darwin | |
| os: macos-latest | |
| - target: aarch64-apple-darwin | |
| os: macos-latest | |
| - target: x86_64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| - target: aarch64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| - target: x86_64-pc-windows-msvc | |
| os: windows-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.target }} | |
| # Cross-compilation support for aarch64-linux | |
| # boring-sys2 builds BoringSSL from C source via cmake — needs cross-compiler + cmake | |
| - name: Install cross-compilation tools | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu cmake | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV | |
| echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" >> $GITHUB_ENV | |
| echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" >> $GITHUB_ENV | |
| echo "CMAKE_SYSTEM_NAME=Linux" >> $GITHUB_ENV | |
| echo "CMAKE_SYSTEM_PROCESSOR=aarch64" >> $GITHUB_ENV | |
| # BoringSSL build tools for native targets | |
| - name: Install cmake | |
| if: matrix.target != 'aarch64-unknown-linux-gnu' && runner.os == 'Linux' | |
| run: sudo apt-get update && sudo apt-get install -y cmake | |
| - name: Install NASM (Windows) | |
| if: runner.os == 'Windows' | |
| run: | | |
| choco install nasm -y | |
| echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Build | |
| run: cargo build --release --target ${{ matrix.target }} | |
| - name: Package | |
| shell: bash | |
| run: | | |
| tag="${GITHUB_REF#refs/tags/}" | |
| staging="webclaw-${tag}-${{ matrix.target }}" | |
| mkdir "$staging" | |
| # Fail loud if any binary is missing. A silent `|| true` on the | |
| # copy was how v0.4.0 shipped tarballs that lacked webclaw-server — | |
| # don't repeat that mistake. If a future binary gets renamed or | |
| # removed, this step should scream, not quietly publish an | |
| # incomplete release. | |
| if [[ "${{ matrix.os }}" == "windows-latest" ]]; then | |
| cp target/${{ matrix.target }}/release/webclaw.exe "$staging/" | |
| cp target/${{ matrix.target }}/release/webclaw-mcp.exe "$staging/" | |
| cp target/${{ matrix.target }}/release/webclaw-server.exe "$staging/" | |
| cp README.md LICENSE "$staging/" | |
| 7z a -tzip "$staging.zip" "$staging" | |
| echo "ASSET=$staging.zip" >> $GITHUB_ENV | |
| else | |
| cp target/${{ matrix.target }}/release/webclaw "$staging/" | |
| cp target/${{ matrix.target }}/release/webclaw-mcp "$staging/" | |
| cp target/${{ matrix.target }}/release/webclaw-server "$staging/" | |
| cp README.md LICENSE "$staging/" | |
| tar czf "$staging.tar.gz" "$staging" | |
| echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV | |
| fi | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.target }} | |
| path: ${{ env.ASSET }} | |
| release: | |
| name: Release | |
| if: github.event_name == 'push' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| path: artifacts | |
| - name: Compute checksums | |
| run: | | |
| cd artifacts | |
| find . -name '*.tar.gz' -exec mv {} . \; | |
| find . -name '*.zip' -exec mv {} . \; | |
| sha256sum *.tar.gz *.zip > SHA256SUMS 2>/dev/null || sha256sum * > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Create GitHub Release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| tag="${GITHUB_REF#refs/tags/}" | |
| gh release create "$tag" \ | |
| artifacts/*.tar.gz \ | |
| artifacts/*.zip \ | |
| artifacts/SHA256SUMS \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --generate-notes | |
| docker: | |
| name: Docker | |
| needs: release | |
| # Runs after a successful release on tag push, or standalone via | |
| # workflow_dispatch to (re)publish an existing tag's image. `always()` lets | |
| # it run even though `release` is skipped on a manual dispatch. | |
| if: ${{ always() && (github.event_name == 'workflow_dispatch' || needs.release.result == 'success') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # The pushed tag, or the workflow_dispatch input for a manual re-publish. | |
| - name: Resolve tag | |
| id: tag | |
| run: echo "tag=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT" | |
| # Download pre-built binaries into TARGETARCH-named dirs (amd64/arm64) so | |
| # a single multi-platform build picks the matching binary per platform. | |
| - name: Download release binaries | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| declare -A arch=( [x86_64-unknown-linux-gnu]=amd64 [aarch64-unknown-linux-gnu]=arm64 ) | |
| for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do | |
| dir="webclaw-${tag}-${target}" | |
| curl -sSL "https://github.com/0xMassi/webclaw/releases/download/${tag}/${dir}.tar.gz" -o "${target}.tar.gz" | |
| tar xzf "${target}.tar.gz" | |
| a="${arch[$target]}" | |
| mkdir -p "binaries-${a}" | |
| cp "${dir}/webclaw" "${dir}/webclaw-mcp" "${dir}/webclaw-server" "binaries-${a}/" | |
| chmod +x "binaries-${a}"/* | |
| done | |
| ls -laR binaries-*/ | |
| # One atomic multi-platform build + push. buildx assembles a single | |
| # manifest list and pushes it in one shot, so there is no separate | |
| # `imagetools create` step to race GHCR's read-after-write (that is what | |
| # failed before: "v0.6.9-arm64: not found"). Provenance/SBOM attestations | |
| # are disabled so each platform entry stays a plain image manifest. | |
| - name: Build and push | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| docker buildx build -f Dockerfile.ci \ | |
| --platform linux/amd64,linux/arm64 \ | |
| --provenance=false --sbom=false \ | |
| -t "ghcr.io/0xmassi/webclaw:${tag}" \ | |
| -t ghcr.io/0xmassi/webclaw:latest \ | |
| --push . | |
| homebrew: | |
| name: Update Homebrew | |
| needs: [release, docker] | |
| # Runs once Docker succeeds, on both tag push and manual re-publish. | |
| if: ${{ always() && needs.docker.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Compute all checksums and update formula | |
| env: | |
| COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| tag="${{ github.event.inputs.tag || github.ref_name }}" | |
| base="https://github.com/0xMassi/webclaw/releases/download/${tag}" | |
| # Download all tarballs (Linux + macOS) and compute SHAs | |
| for target in aarch64-apple-darwin x86_64-apple-darwin aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu; do | |
| curl -sSL "${base}/webclaw-${tag}-${target}.tar.gz" -o "${target}.tar.gz" | |
| done | |
| SHA_MAC_ARM=$(sha256sum aarch64-apple-darwin.tar.gz | cut -d' ' -f1) | |
| SHA_MAC_X86=$(sha256sum x86_64-apple-darwin.tar.gz | cut -d' ' -f1) | |
| SHA_LINUX_ARM=$(sha256sum aarch64-unknown-linux-gnu.tar.gz | cut -d' ' -f1) | |
| SHA_LINUX_X86=$(sha256sum x86_64-unknown-linux-gnu.tar.gz | cut -d' ' -f1) | |
| echo "macOS arm64: $SHA_MAC_ARM" | |
| echo "macOS x86_64: $SHA_MAC_X86" | |
| echo "Linux arm64: $SHA_LINUX_ARM" | |
| echo "Linux x86_64: $SHA_LINUX_X86" | |
| # Generate formula | |
| cat > webclaw.rb << FORMULA | |
| class Webclaw < Formula | |
| desc "The fastest web scraper for AI agents. 67% fewer tokens. Sub-ms extraction." | |
| homepage "https://webclaw.io" | |
| license "AGPL-3.0" | |
| version "${tag#v}" | |
| on_macos do | |
| if Hardware::CPU.arm? | |
| url "${base}/webclaw-${tag}-aarch64-apple-darwin.tar.gz" | |
| sha256 "${SHA_MAC_ARM}" | |
| else | |
| url "${base}/webclaw-${tag}-x86_64-apple-darwin.tar.gz" | |
| sha256 "${SHA_MAC_X86}" | |
| end | |
| end | |
| on_linux do | |
| if Hardware::CPU.arm? | |
| url "${base}/webclaw-${tag}-aarch64-unknown-linux-gnu.tar.gz" | |
| sha256 "${SHA_LINUX_ARM}" | |
| else | |
| url "${base}/webclaw-${tag}-x86_64-unknown-linux-gnu.tar.gz" | |
| sha256 "${SHA_LINUX_X86}" | |
| end | |
| end | |
| def install | |
| bin.install "webclaw" | |
| bin.install "webclaw-mcp" | |
| bin.install "webclaw-server" | |
| end | |
| test do | |
| assert_match "webclaw", shell_output("#{bin}/webclaw --version") | |
| end | |
| end | |
| FORMULA | |
| # Remove leading whitespace from heredoc | |
| sed -i 's/^ //' webclaw.rb | |
| # Push to homebrew tap | |
| git clone "https://x-access-token:${COMMITTER_TOKEN}@github.com/0xMassi/homebrew-webclaw.git" tap | |
| cp webclaw.rb tap/Formula/webclaw.rb | |
| cd tap | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/webclaw.rb | |
| git diff --cached --quiet || git commit -m "Update webclaw to ${tag}" | |
| git push |