Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions .github/workflows/docker-publish-knots-bip110.yml
Original file line number Diff line number Diff line change
@@ -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
99 changes: 89 additions & 10 deletions docker/knots/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]
Loading