Simplify reproducible builds: single version tag, add binary tarball + AppImage outputs

- Replace docker-reproducible.yml with reproducible.yml which produces
  three artifacts per arch: Docker image, binary tarball, and AppImage
- Use a single multi-arch index digest in Dockerfile.reproducible as the
  sole version tag to maintain; Makefile and CI no longer carry their own
  per-arch image references
- Add packaging/appimage/ template (AppRun, .desktop, lighthouse.svg)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
antondlr
2026-04-20 10:55:27 +02:00
parent c028bac28d
commit b647e22861
7 changed files with 316 additions and 191 deletions

View File

@@ -1,183 +0,0 @@
name: docker-reproducible
on:
push:
branches:
- unstable
tags:
- v*
workflow_dispatch: # allows manual triggering for testing purposes and skips publishing an image
env:
DOCKER_REPRODUCIBLE_IMAGE_NAME: >-
${{ github.repository_owner }}/lighthouse-reproducible
DOCKER_PASSWORD: ${{ secrets.DH_KEY }}
DOCKER_USERNAME: ${{ secrets.DH_ORG }}
jobs:
extract-version:
name: extract version
runs-on: ubuntu-22.04
steps:
- name: Extract version
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
# It's a tag (e.g., v1.2.3)
VERSION="${GITHUB_REF#refs/tags/}"
elif [[ "${{ github.ref }}" == refs/heads/unstable ]]; then
# unstable branch -> latest-unstable
VERSION="latest-unstable"
else
# For manual triggers from other branches and will not publish any image
VERSION="test-build"
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
id: extract_version
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
verify-and-build:
name: verify reproducibility and build
needs: extract-version
strategy:
matrix:
arch: [amd64, arm64]
include:
- arch: amd64
rust_target: x86_64-unknown-linux-gnu
rust_image: >-
rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816
platform: linux/amd64
runner: ubuntu-22.04
- arch: arm64
rust_target: aarch64-unknown-linux-gnu
rust_image: >-
rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
platform: linux/arm64
runner: ubuntu-22.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker
- name: Verify reproducible builds (${{ matrix.arch }})
run: |
# Build first image
docker build -f Dockerfile.reproducible \
--platform ${{ matrix.platform }} \
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
--build-arg RUST_IMAGE="${{ matrix.rust_image }}" \
-t lighthouse-verify-1-${{ matrix.arch }} .
# Extract binary from first build
docker create --name extract-1-${{ matrix.arch }} lighthouse-verify-1-${{ matrix.arch }}
docker cp extract-1-${{ matrix.arch }}:/lighthouse ./lighthouse-1-${{ matrix.arch }}
docker rm extract-1-${{ matrix.arch }}
# Clean state for second build
docker buildx prune -f
docker system prune -f
# Build second image
docker build -f Dockerfile.reproducible \
--platform ${{ matrix.platform }} \
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
--build-arg RUST_IMAGE="${{ matrix.rust_image }}" \
-t lighthouse-verify-2-${{ matrix.arch }} .
# Extract binary from second build
docker create --name extract-2-${{ matrix.arch }} lighthouse-verify-2-${{ matrix.arch }}
docker cp extract-2-${{ matrix.arch }}:/lighthouse ./lighthouse-2-${{ matrix.arch }}
docker rm extract-2-${{ matrix.arch }}
# Compare binaries
echo "=== Comparing binaries ==="
echo "Build 1 SHA256: $(sha256sum lighthouse-1-${{ matrix.arch }})"
echo "Build 2 SHA256: $(sha256sum lighthouse-2-${{ matrix.arch }})"
if cmp lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }}; then
echo "Reproducible build verified for ${{ matrix.arch }}"
else
echo "Reproducible build FAILED for ${{ matrix.arch }}"
echo "BLOCKING RELEASE: Builds are not reproducible!"
echo "First 10 differences:"
cmp -l lighthouse-1-${{ matrix.arch }} lighthouse-2-${{ matrix.arch }} | head -10
exit 1
fi
# Clean up verification artifacts but keep one image for publishing
rm -f lighthouse-*-${{ matrix.arch }}
docker rmi lighthouse-verify-1-${{ matrix.arch }} || true
# Re-tag the second image for publishing (we verified it's identical to first)
VERSION=${{ needs.extract-version.outputs.VERSION }}
FINAL_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
docker tag lighthouse-verify-2-${{ matrix.arch }} "$FINAL_TAG"
- name: Log in to Docker Hub
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Push verified image (${{ matrix.arch }})
if: ${{ github.event_name != 'workflow_dispatch' }}
run: |
VERSION=${{ needs.extract-version.outputs.VERSION }}
IMAGE_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}"
docker push "$IMAGE_TAG"
- name: Clean up local images
run: |
docker rmi lighthouse-verify-2-${{ matrix.arch }} || true
VERSION=${{ needs.extract-version.outputs.VERSION }}
docker rmi "${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}" || true
- name: Upload verification artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: verification-failure-${{ matrix.arch }}
path: |
lighthouse-*-${{ matrix.arch }}
create-manifest:
name: create multi-arch manifest
runs-on: ubuntu-22.04
needs: [extract-version, verify-and-build]
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Create and push multi-arch manifest
run: |
IMAGE_NAME=${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}
VERSION=${{ needs.extract-version.outputs.VERSION }}
# Create manifest for the version tag
docker manifest create \
${IMAGE_NAME}:${VERSION} \
${IMAGE_NAME}:${VERSION}-amd64 \
${IMAGE_NAME}:${VERSION}-arm64
docker manifest push ${IMAGE_NAME}:${VERSION}
# For version tags, also create/update the latest tag to keep stable up to date
# Only create latest tag for proper release versions (e.g. v1.2.3, not v1.2.3-alpha)
if [[ "${GITHUB_REF}" == refs/tags/* ]] && [[ "${VERSION}" =~ ^v[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}$ ]]; then
docker manifest create \
${IMAGE_NAME}:latest \
${IMAGE_NAME}:${VERSION}-amd64 \
${IMAGE_NAME}:${VERSION}-arm64
docker manifest push ${IMAGE_NAME}:latest
fi

288
.github/workflows/reproducible.yml vendored Normal file
View File

@@ -0,0 +1,288 @@
name: reproducible
# Produces three artifacts per architecture: Docker image, binary tarball, AppImage.
# The Docker image is the single build artifact; the binary and AppImage are extracted from it.
# Only one version tag to maintain: the RUST_IMAGE ARG in Dockerfile.reproducible.
on:
push:
branches:
- unstable
tags:
- v*
workflow_dispatch:
env:
DOCKER_REPRODUCIBLE_IMAGE_NAME: ${{ github.repository_owner }}/lighthouse-reproducible
DOCKER_PASSWORD: ${{ secrets.DH_KEY }}
DOCKER_USERNAME: ${{ secrets.DH_ORG }}
jobs:
extract-version:
name: extract version
runs-on: ubuntu-22.04
steps:
- name: Extract version
id: extract_version
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF#refs/tags/}"
elif [[ "${{ github.ref }}" == refs/heads/unstable ]]; then
VERSION="latest-unstable"
else
VERSION="test-build"
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
build:
name: build and verify (${{ matrix.arch }})
needs: extract-version
strategy:
matrix:
arch: [amd64, arm64]
include:
- arch: amd64
rust_target: x86_64-unknown-linux-gnu
platform: linux/amd64
runner: ubuntu-22.04
appimage_arch: x86_64
- arch: arm64
rust_target: aarch64-unknown-linux-gnu
platform: linux/arm64
runner: ubuntu-22.04-arm
appimage_arch: aarch64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker
# ── Step 1: Build twice and verify bit-for-bit reproducibility ──────────
- name: Build image (pass 1)
run: |
docker build -f Dockerfile.reproducible \
--platform ${{ matrix.platform }} \
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
-t lighthouse-verify-1 .
- name: Extract binary (pass 1)
run: |
docker create --name extract-1 lighthouse-verify-1
docker cp extract-1:/lighthouse ./lighthouse-1
docker rm extract-1
- name: Clean Docker state between builds
run: |
docker buildx prune -f
docker system prune -f
- name: Build image (pass 2)
run: |
docker build -f Dockerfile.reproducible \
--platform ${{ matrix.platform }} \
--build-arg RUST_TARGET="${{ matrix.rust_target }}" \
-t lighthouse-verify-2 .
- name: Extract binary (pass 2)
run: |
docker create --name extract-2 lighthouse-verify-2
docker cp extract-2:/lighthouse ./lighthouse-2
docker rm extract-2
- name: Verify reproducibility
run: |
echo "Pass 1 SHA256: $(sha256sum lighthouse-1)"
echo "Pass 2 SHA256: $(sha256sum lighthouse-2)"
if cmp lighthouse-1 lighthouse-2; then
echo "Reproducible build verified for ${{ matrix.arch }}"
else
echo "BLOCKING RELEASE: builds are not reproducible!"
echo "First 10 differing bytes:"
cmp -l lighthouse-1 lighthouse-2 | head -10
exit 1
fi
# ── Step 2: Tag the verified image and push ──────────────────────────────
- name: Tag verified image
run: |
VERSION=${{ needs.extract-version.outputs.VERSION }}
docker tag lighthouse-verify-2 \
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}
- name: Log in to Docker Hub
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Push Docker image
if: ${{ github.event_name != 'workflow_dispatch' }}
run: |
VERSION=${{ needs.extract-version.outputs.VERSION }}
docker push ${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}
# ── Step 3: Binary tarball ───────────────────────────────────────────────
- name: Create binary tarball
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
run: |
cp lighthouse-2 lighthouse
tar -czf lighthouse-${VERSION}-${{ matrix.rust_target }}.tar.gz lighthouse
sha256sum lighthouse-${VERSION}-${{ matrix.rust_target }}.tar.gz \
> lighthouse-${VERSION}-${{ matrix.rust_target }}.tar.gz.sha256
# ── Step 4: AppImage ─────────────────────────────────────────────────────
- name: Download appimagetool
run: |
# Pin appimagetool by release tag for reproducibility
curl -fsSL \
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage_arch }}.AppImage" \
-o appimagetool
chmod +x appimagetool
- name: Assemble AppDir
run: |
mkdir -p AppDir/usr/bin
cp lighthouse-2 AppDir/usr/bin/lighthouse
cp packaging/appimage/AppRun AppDir/AppRun
chmod +x AppDir/AppRun
cp packaging/appimage/lighthouse.desktop AppDir/lighthouse.desktop
cp packaging/appimage/lighthouse.svg AppDir/lighthouse.svg
- name: Build AppImage
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
# Deterministic squashfs: fixed modification times, no extra metadata
SOURCE_DATE_EPOCH: 0
run: |
./appimagetool \
--comp xz \
AppDir \
lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage
sha256sum lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage \
> lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage.sha256
# ── Step 5: GPG sign and upload artifacts (tags only) ────────────────────
- name: Sign artifacts
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
VERSION: ${{ needs.extract-version.outputs.VERSION }}
run: |
echo "$GPG_SIGNING_KEY" | gpg --batch --import
for f in \
lighthouse-${VERSION}-${{ matrix.rust_target }}.tar.gz \
lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage; do
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 \
--pinentry-mode loopback --batch -ab "$f"
done
- name: Upload binary tarball
uses: actions/upload-artifact@v4
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.rust_target }}.tar.gz
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.rust_target }}.tar.gz
compression-level: 0
- name: Upload binary tarball signature
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.rust_target }}.tar.gz.asc
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.rust_target }}.tar.gz.asc
compression-level: 0
- name: Upload AppImage
uses: actions/upload-artifact@v4
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.appimage_arch }}.AppImage
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.appimage_arch }}.AppImage
compression-level: 0
- name: Upload AppImage signature
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.appimage_arch }}.AppImage.asc
path: lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.appimage_arch }}.AppImage.asc
compression-level: 0
- name: Upload verification artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: verification-failure-${{ matrix.arch }}
path: |
lighthouse-1
lighthouse-2
- name: Clean up
if: always()
run: |
docker rmi lighthouse-verify-1 lighthouse-verify-2 || true
VERSION=${{ needs.extract-version.outputs.VERSION }}
docker rmi ${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }} || true
manifest:
name: create multi-arch manifest
needs: [extract-version, build]
runs-on: ubuntu-22.04
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Create and push multi-arch manifest
run: |
IMAGE=${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}
VERSION=${{ needs.extract-version.outputs.VERSION }}
docker manifest create ${IMAGE}:${VERSION} \
${IMAGE}:${VERSION}-amd64 \
${IMAGE}:${VERSION}-arm64
docker manifest push ${IMAGE}:${VERSION}
# Tag latest only for stable release versions (e.g. v1.2.3, not v1.2.3-alpha)
if [[ "${GITHUB_REF}" == refs/tags/* ]] && \
[[ "${VERSION}" =~ ^v[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}$ ]]; then
docker manifest create ${IMAGE}:latest \
${IMAGE}:${VERSION}-amd64 \
${IMAGE}:${VERSION}-arm64
docker manifest push ${IMAGE}:latest
fi
publish:
name: publish to GitHub release
needs: [extract-version, build]
runs-on: ubuntu-22.04
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Upload to GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${VERSION} \
lighthouse-${VERSION}-x86_64-unknown-linux-gnu.tar.gz/lighthouse-${VERSION}-x86_64-unknown-linux-gnu.tar.gz \
lighthouse-${VERSION}-x86_64-unknown-linux-gnu.tar.gz.asc/lighthouse-${VERSION}-x86_64-unknown-linux-gnu.tar.gz.asc \
lighthouse-${VERSION}-aarch64-unknown-linux-gnu.tar.gz/lighthouse-${VERSION}-aarch64-unknown-linux-gnu.tar.gz \
lighthouse-${VERSION}-aarch64-unknown-linux-gnu.tar.gz.asc/lighthouse-${VERSION}-aarch64-unknown-linux-gnu.tar.gz.asc \
lighthouse-${VERSION}-x86_64.AppImage/lighthouse-${VERSION}-x86_64.AppImage \
lighthouse-${VERSION}-x86_64.AppImage.asc/lighthouse-${VERSION}-x86_64.AppImage.asc \
lighthouse-${VERSION}-aarch64.AppImage/lighthouse-${VERSION}-aarch64.AppImage \
lighthouse-${VERSION}-aarch64.AppImage.asc/lighthouse-${VERSION}-aarch64.AppImage.asc \
--repo ${{ github.repository }}

View File

@@ -1,5 +1,8 @@
# Define the Rust image as an argument with a default to x86_64 Rust 1.88 image based on Debian Bullseye
ARG RUST_IMAGE="rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816"
# Single version tag to maintain for reproducible builds.
# This multi-arch index digest resolves to the correct arch-specific image at build time.
# To update: run `docker manifest inspect rust:X.Y-bullseye --verbose` and replace the digest below.
# rust:1.88-bullseye
ARG RUST_IMAGE="rust:1.88-bullseye@sha256:60c95b78b164bc809090509235ab00797a07740fe8733b48593cd42de72b5ee1"
FROM ${RUST_IMAGE} AS builder
# Install specific version of the build dependencies

View File

@@ -118,10 +118,6 @@ JEMALLOC_OVERRIDE = /usr/lib/$(JEMALLOC_LIB_ARCH)-linux-gnu/libjemalloc.a
# Default target architecture
RUST_TARGET ?= x86_64-unknown-linux-gnu
# Default images for different architectures
RUST_IMAGE_AMD64 ?= rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816
RUST_IMAGE_ARM64 ?= rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94
.PHONY: build-reproducible
build-reproducible: ## Build the lighthouse binary into `target` directory with reproducible builds
SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
@@ -132,11 +128,13 @@ build-reproducible: ## Build the lighthouse binary into `target` directory with
JEMALLOC_OVERRIDE=${JEMALLOC_OVERRIDE} \
cargo build --bin lighthouse --features "$(FEATURES_REPRODUCIBLE)" --profile "$(PROFILE)" --locked --target $(RUST_TARGET)
# Rust image digest is the single source of truth in Dockerfile.reproducible.
# These targets pass no --build-arg RUST_IMAGE so Docker uses the ARG default from that file.
.PHONY: build-reproducible-x86_64
build-reproducible-x86_64: ## Build reproducible x86_64 Docker image
DOCKER_BUILDKIT=1 docker build \
--build-arg RUST_TARGET="x86_64-unknown-linux-gnu" \
--build-arg RUST_IMAGE=$(RUST_IMAGE_AMD64) \
-f Dockerfile.reproducible \
-t lighthouse:reproducible-amd64 .
@@ -145,7 +143,6 @@ build-reproducible-aarch64: ## Build reproducible aarch64 Docker image
DOCKER_BUILDKIT=1 docker build \
--platform linux/arm64 \
--build-arg RUST_TARGET="aarch64-unknown-linux-gnu" \
--build-arg RUST_IMAGE=$(RUST_IMAGE_ARM64) \
-f Dockerfile.reproducible \
-t lighthouse:reproducible-arm64 .

View File

@@ -0,0 +1,9 @@
#!/bin/sh
# AppRun - AppImage entry point for Lighthouse
# Resolves the real location of the AppImage and executes the bundled binary.
set -e
SELF=$(readlink -f "$0")
HERE=$(dirname "$SELF")
exec "$HERE/usr/bin/lighthouse" "$@"

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Name=Lighthouse
Comment=Ethereum consensus client
Exec=lighthouse
Icon=lighthouse
Type=Application
Categories=Network;
Terminal=true

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47 51" fill="#fff">
<path d="M34.6763 27.5954C34.4546 29.842 33.6617 30.8785 32.2989 32.5242L37.3878 37.603L35.6756 39.3118L30.5867 34.233C28.8745 35.593 26.9251 36.4012 24.7371 36.6533V43.8204H22.3597V36.6533C20.1409 36.3998 18.1901 35.593 16.5101 34.233L11.4184 39.3131L9.70623 37.6044L14.7951 32.5256C14.0976 31.7035 13.5587 30.8014 13.1784 29.8196C12.798 28.8391 12.5286 27.7943 12.37 26.6863H5.18854V24.3137H12.37C12.5286 23.2057 12.798 22.1777 13.1784 21.228C13.5587 20.2476 14.0976 19.3288 14.7951 18.4744L9.70623 13.3956L11.4184 11.6869L16.5073 16.7656C18.1564 15.4056 20.1058 14.6142 22.3569 14.3929V7.17818H24.7343V14.3929C26.9532 14.6464 28.904 15.4378 30.5839 16.7656L35.6728 11.6869L37.385 13.3956L32.2961 18.4744C33.6588 20.1509 34.4518 21.1888 34.6735 23.4032H46.9972C45.9376 11.4081 35.844 2 23.547 2C10.5427 2 0 12.5216 0 25.5C0 38.4784 10.5427 49 23.547 49C35.844 49 45.9376 39.5919 47 27.5954H34.6763Z"/>
</svg>

After

Width:  |  Height:  |  Size: 986 B