diff --git a/.github/workflows/docker-publish-knots-bip110.yml b/.github/workflows/docker-publish-knots-bip110.yml new file mode 100644 index 0000000..cd577cd --- /dev/null +++ b/.github/workflows/docker-publish-knots-bip110.yml @@ -0,0 +1,158 @@ +name: Docker Publish Knots BIP-110 + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Version for tarball naming and image tags + KNOTS_VERSION: "29.2.knots20251110" + # GitHub release info + GITHUB_REPO: "dathonohm/bitcoin" + GITHUB_TAG: "v29.2.knots20251110+bip110-v0.1rc2" + # Dathon Ohm's GPG key + GPG_KEYS: "2E3A66FF67F98B4F" + # Image naming + IMAGE_NAME: "knots-bip110" + IMAGE_TAG: "29.2.knots20251110-bip110-v0.1rc2" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # Needed to push to GHCR + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: ./docker/knots + file: ./docker/knots/Dockerfile + push: ${{ github.event_name == 'push' }} + platforms: ${{ matrix.platform }} + outputs: type=image,push-by-digest=true,name=ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }},push=${{ github.event_name == 'push' }} + build-args: | + KNOTS_VERSION=${{ env.KNOTS_VERSION }} + SOURCE=github + GITHUB_REPO=${{ env.GITHUB_REPO }} + GITHUB_TAG=${{ env.GITHUB_TAG }} + GPG_KEYS=${{ env.GPG_KEYS }} + IMAGE_TITLE=Bitcoin Knots BIP-110 + IMAGE_DESCRIPTION=Bitcoin Knots with BIP-110 UASF support + IMAGE_SOURCE=https://github.com/${{ env.GITHUB_REPO }} + IMAGE_DOCUMENTATION=https://bip110.org/ + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Export digest + if: github.event_name == 'push' + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + if: github.event_name == 'push' + uses: actions/upload-artifact@v4 + with: + name: digests-bip110-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + needs: build + if: github.event_name == 'push' # Only run on push events + runs-on: ubuntu-latest + permissions: + packages: write # Needed to push to GHCR + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-bip110-* + merge-multiple: true + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create and push manifest + working-directory: ${{ runner.temp }}/digests + run: | + # Create version-specific tag + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \ + $(printf 'ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + # Create latest tag + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest \ + $(printf 'ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + sign-image: + needs: merge + if: github.event_name == 'push' # Only run on push events + runs-on: ubuntu-latest + permissions: + packages: write # Needed to push signature to GHCR + id-token: write # Needed for keyless signing with Cosign/Sigstore + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + - name: Sign the multi-arch image + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign sign --yes ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + cosign sign --yes ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest diff --git a/docker/knots/Dockerfile b/docker/knots/Dockerfile index 1a5c96a..cabd405 100644 --- a/docker/knots/Dockerfile +++ b/docker/knots/Dockerfile @@ -2,24 +2,86 @@ ARG ALPINE_BUILDER_VERSION=3.18 FROM alpine:3.20 AS verifier +# KNOTS_VERSION: Version string for tarball naming (e.g., "29.2.knots20251010") +# SOURCE: "official" for bitcoinknots.org, "github" for GitHub releases +# GITHUB_REPO: GitHub repo for source="github" (e.g., "dathonohm/bitcoin") +# GITHUB_TAG: GitHub release tag (e.g., "v29.2.knots20251110+bip110-v0.1rc2") +# GPG_KEYS: Space-separated GPG key IDs to import (for github source) ARG KNOTS_VERSION +ARG SOURCE=official +ARG GITHUB_REPO="" +ARG GITHUB_TAG="" +ARG GPG_KEYS="" WORKDIR /tmp -RUN KNOTS_MAJOR_VERSION=$(echo ${KNOTS_VERSION} | cut -c1-2) \ - && wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/SHA256SUMS \ - && wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/SHA256SUMS.asc \ - && wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/bitcoin-${KNOTS_VERSION}.tar.gz - RUN apk add --no-cache \ coreutils \ curl \ gnupg \ gnupg-keyboxd \ - jq \ - && curl -s https://api.github.com/repos/bitcoinknots/guix.sigs/contents/builder-keys | jq -r '.[].download_url' | while read url; do curl -s "$url" | gpg --import; done \ - && gpg --verify SHA256SUMS.asc SHA256SUMS \ - && sha256sum --ignore-missing -c SHA256SUMS + jq + +# Download and verify based on source type +RUN set -ex; \ + if [ "${SOURCE}" = "official" ]; then \ + echo "Downloading from official Bitcoin Knots..."; \ + KNOTS_MAJOR_VERSION=$(echo ${KNOTS_VERSION} | cut -c1-2); \ + wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/SHA256SUMS; \ + wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/SHA256SUMS.asc; \ + wget https://bitcoinknots.org/files/${KNOTS_MAJOR_VERSION}.x/${KNOTS_VERSION}/bitcoin-${KNOTS_VERSION}.tar.gz; \ + # Import all Bitcoin Knots builder keys + curl -s https://api.github.com/repos/bitcoinknots/guix.sigs/contents/builder-keys \ + | jq -r '.[].download_url' \ + | while read url; do curl -s "$url" | gpg --import; done; \ + gpg --verify SHA256SUMS.asc SHA256SUMS; \ + sha256sum --ignore-missing -c SHA256SUMS; \ + elif [ "${SOURCE}" = "github" ]; then \ + echo "Downloading from GitHub: ${GITHUB_REPO}..."; \ + SOURCE_TARBALL="bitcoin-${KNOTS_VERSION}.tar.gz"; \ + # Import specified GPG keys if provided + if [ -n "${GPG_KEYS}" ]; then \ + for key in ${GPG_KEYS}; do \ + echo "Importing GPG key: ${key}"; \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys ${key} || \ + gpg --keyserver keyserver.ubuntu.com --recv-keys ${key} || true; \ + done; \ + fi; \ + # Check if SHA256SUMS exists (some releases don't have them) + if wget -q --spider "https://github.com/${GITHUB_REPO}/releases/download/${GITHUB_TAG}/SHA256SUMS"; then \ + echo "Found SHA256SUMS, will verify checksums..."; \ + wget https://github.com/${GITHUB_REPO}/releases/download/${GITHUB_TAG}/SHA256SUMS; \ + if wget -q --spider "https://github.com/${GITHUB_REPO}/releases/download/${GITHUB_TAG}/SHA256SUMS.asc"; then \ + wget https://github.com/${GITHUB_REPO}/releases/download/${GITHUB_TAG}/SHA256SUMS.asc; \ + gpg --verify SHA256SUMS.asc SHA256SUMS; \ + fi; \ + # Try release tarball first + RELEASE_URL="https://github.com/${GITHUB_REPO}/releases/download/${GITHUB_TAG}/${SOURCE_TARBALL}"; \ + if wget -q "${RELEASE_URL}" -O "${SOURCE_TARBALL}"; then \ + echo "Downloaded release tarball, verifying checksum..."; \ + sha256sum --ignore-missing -c SHA256SUMS; \ + else \ + echo "Release tarball not found, using GitHub archive..."; \ + wget "https://github.com/${GITHUB_REPO}/archive/refs/tags/${GITHUB_TAG}.tar.gz" -O "${SOURCE_TARBALL}"; \ + fi; \ + else \ + echo "No SHA256SUMS found, downloading GitHub auto-generated archive..."; \ + echo "WARNING: Building from unverified source archive"; \ + wget "https://github.com/${GITHUB_REPO}/archive/refs/tags/${GITHUB_TAG}.tar.gz" -O "${SOURCE_TARBALL}"; \ + # Verify git tag signature if GPG keys were provided + if [ -n "${GPG_KEYS}" ]; then \ + echo "Attempting to verify git tag signature..."; \ + apk add --no-cache git; \ + git clone --depth 1 --branch "${GITHUB_TAG}" "https://github.com/${GITHUB_REPO}.git" repo-verify || true; \ + if [ -d "repo-verify" ]; then \ + cd repo-verify && git verify-tag "${GITHUB_TAG}" && cd .. && rm -rf repo-verify; \ + fi; \ + fi; \ + fi; \ + else \ + echo "Unknown SOURCE: ${SOURCE}"; \ + exit 1; \ + fi FROM alpine:${ALPINE_BUILDER_VERSION} AS builder @@ -47,7 +109,12 @@ RUN apk add --no-cache \ miniupnpc-dev \ zeromq-dev -RUN tar zxf bitcoin-${KNOTS_VERSION}.tar.gz +# Extract and normalize directory name (GitHub archives have different naming) +RUN tar zxf bitcoin-${KNOTS_VERSION}.tar.gz \ + && EXTRACTED_DIR=$(tar tzf bitcoin-${KNOTS_VERSION}.tar.gz | head -1 | cut -d/ -f1) \ + && if [ "${EXTRACTED_DIR}" != "bitcoin-${KNOTS_VERSION}" ]; then \ + mv "${EXTRACTED_DIR}" bitcoin-${KNOTS_VERSION}; \ + fi RUN make -C bitcoin-${KNOTS_VERSION}/depends -j$(nproc) NO_QT=1 NO_NATPMP=1 NO_UPNP=1 NO_USDT=1 @@ -82,6 +149,11 @@ FROM alpine:3.20 AS final ARG KNOTS_VERSION ARG USER=bitcoind ARG DIR=/data +# Optional labels for image metadata +ARG IMAGE_TITLE="Bitcoin Knots" +ARG IMAGE_DESCRIPTION="Bitcoin Knots full node" +ARG IMAGE_SOURCE="https://github.com/bitcoinknots/bitcoin" +ARG IMAGE_DOCUMENTATION="" COPY --from=builder /usr/local/bin/* /usr/local/bin/ COPY --from=builder /tmp/bitcoin-${KNOTS_VERSION}/test/functional/test_framework /opt/bitcoin/test/functional/test_framework @@ -122,5 +194,12 @@ EXPOSE 8332 18332 18443 38332 # ZMQ ports (for block hashes, raw blocks & raw transactions respectively) EXPOSE 8443 28332 28333 +# Image labels +LABEL org.opencontainers.image.title="${IMAGE_TITLE}" +LABEL org.opencontainers.image.description="${IMAGE_DESCRIPTION}" +LABEL org.opencontainers.image.source="${IMAGE_SOURCE}" +LABEL org.opencontainers.image.documentation="${IMAGE_DOCUMENTATION}" +LABEL org.opencontainers.image.version="${KNOTS_VERSION}" + # Set the entrypoint script ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file