diff --git a/.github/workflows/release-bitcoind.yml b/.github/workflows/release-bitcoind.yml new file mode 100644 index 0000000..f9a5926 --- /dev/null +++ b/.github/workflows/release-bitcoind.yml @@ -0,0 +1,19 @@ +name: Release bitcoind image +on: + push: + branches: [main] + tags: ["*"] + paths: ["images/bitcoind/**"] + pull_request: + branches: [main] + paths: ["images/bitcoind/**"] + +jobs: + call: + uses: ./.github/workflows/release-image.yml + with: + image: ghcr.io/${{ github.repository }}/bitcoind + context: images/bitcoind + dockerfile: Dockerfile + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-dogecoind.yml b/.github/workflows/release-dogecoind.yml new file mode 100644 index 0000000..b18a1c9 --- /dev/null +++ b/.github/workflows/release-dogecoind.yml @@ -0,0 +1,19 @@ +name: Release dogecoind image +on: + push: + branches: [main] + tags: ["*"] + paths: ["images/dogecoind/**"] + pull_request: + branches: [main] + paths: ["images/dogecoind/**"] + +jobs: + call: + uses: ./.github/workflows/release-image.yml + with: + image: ghcr.io/${{ github.repository }}/dogecoind + context: images/dogecoind + dockerfile: Dockerfile + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml new file mode 100644 index 0000000..6ecdae1 --- /dev/null +++ b/.github/workflows/release-image.yml @@ -0,0 +1,141 @@ +on: + workflow_call: + inputs: + image: + required: true + type: string + context: + required: true + type: string + dockerfile: + required: false + type: string + default: Dockerfile + secrets: + registry-password: + required: true + +permissions: + contents: read + packages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + outputs: + digest: ${{ steps.build-and-push.outputs.digest }} + tags: ${{ steps.meta.outputs.tags }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get image tag (from Dockerfile ARG VERSION) + id: get_image_tag + run: | + grep '^ARG VERSION=' "${{ inputs.context }}/$GITHUB_WORKFLOW" >/dev/null 2>&1 || true + RAW_VERSION=$(grep -m1 '^ARG VERSION=' "${{ inputs.context }}/${{ inputs.dockerfile }}" \ + | cut -d'=' -f2 | tr -d '"' | tr -d "'" | tr -d '[:space:]') + VERSION_FOR_TAG=${RAW_VERSION#v} + echo "image_tag=${VERSION_FOR_TAG}" >> $GITHUB_OUTPUT + echo "VERSION_FOR_GIT=${RAW_VERSION}" >> $GITHUB_ENV + echo "VERSION_FOR_TAG=${VERSION_FOR_TAG}" >> $GITHUB_ENV + + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad + with: + cosign-release: "v2.5.3" + + - name: Setup Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.registry-password }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ inputs.image }} + tags: | + type=raw,value=${{ env.VERSION_FOR_TAG }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: Build and push + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: ${{ inputs.context }} + file: ${{ inputs.context }}/${{ inputs.dockerfile }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Sign published image (keyless / certificate-based) + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: 1 + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: | + echo "${TAGS}" | xargs -n1 -I {} cosign sign --yes {}@${DIGEST} + + - name: Verify signatures + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: 1 + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: | + echo "${TAGS}" | while read -r tag; do + cosign verify \ + --certificate-identity="https://github.com/${{ github.repository }}/.github/workflows/release-image.yml@${{ github.ref }}" \ + --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ + "${tag}@${DIGEST}" + done + + # - name: Sign the published Docker image + # if: ${{ github.event_name != 'pull_request' }} + # env: + # TAGS: ${{ steps.meta.outputs.tags }} + # DIGEST: ${{ steps.build-and-push.outputs.digest }} + # run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + # - name: Verify ghcr image signatures + # if: ${{ github.event_name != 'pull_request' }} + # shell: bash + # env: + # COSIGN_EXPERIMENTAL: 1 + # TAGS: ${{ steps.meta.outputs.tags }} + # DIGEST: ${{ steps.build-and-push.outputs.digest }} + # run: | + # echo "${TAGS}" | xargs -I {} cosign verify \ + # --certificate-identity=https://github.com/${{ github.repository }}/.github/workflows/release-dogecoind.yml@${{ github.ref }} \ + # --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + # "{}@${DIGEST}" + generate-provenance: + needs: [build] + if: ${{ github.event_name != 'pull_request' }} + permissions: + actions: read + id-token: write + packages: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 + with: + image: ${{ inputs.image }} + digest: ${{ needs.build.outputs.digest }} + registry-username: ${{ github.actor }} + secrets: + registry-password: ${{ secrets.registry-password }} diff --git a/.github/workflows/release-rippled.yml b/.github/workflows/release-rippled.yml new file mode 100644 index 0000000..36b54e6 --- /dev/null +++ b/.github/workflows/release-rippled.yml @@ -0,0 +1,19 @@ +name: Release rippled image +on: + push: + branches: [main] + tags: ["*"] + paths: ["images/rippled/**"] + pull_request: + branches: [main] + paths: ["images/rippled/**"] + +jobs: + call: + uses: ./.github/workflows/release-image.yml + with: + image: ghcr.io/${{ github.repository }}/rippled + context: images/rippled + dockerfile: Dockerfile + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index b55642a..33ffaad 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/flare-foundation/connected-chains-docker/badge)](https://scorecard.dev/viewer/?uri=github.com/flare-foundation/connected-chains-docker) > [!IMPORTANT] -> Images have been updated to follow rootless and distroless best practices. For existing configurations, follow these instructions after updating: [Update volume permissions](#update-volume-permissions).** +> Images have been updated to follow rootless and distroless best practices. For existing configurations, follow these instructions after updating: [Update volume permissions](#update-volume-permissions).\*\* A quickstart repo filled with Docker images which allows people to get up and running with the chains connected to the Flare Network. The following nodes are included: + - [Bitcoin](https://github.com/bitcoin/bitcoin) - [Litecoin](https://github.com/litecoin-project/litecoin) - [Dogecoin](https://github.com/dogecoin/dogecoin) @@ -27,16 +28,15 @@ The following specifications were observed to be able to run all nodes on a sing Bootstrap time depends on your infrastructure and network, in our testing it is a few hours for litecoin, dogecoin, algorand and xrpl, more than a day for bitcoin. - As of Q1 2024, this is roughly what you can expect from each node regarding disk usage: -| Volume | Size | -| ----------- | ----------- | -| algorand-data | 200GB | -| bitcoin-data | 1000GB | -| dogecoin-data | 350GB | -| litecoin-data | 300GB | -| ripple-data | 400GB | +| Volume | Size | +| ------------- | ------ | +| algorand-data | 200GB | +| bitcoin-data | 1000GB | +| dogecoin-data | 350GB | +| litecoin-data | 300GB | +| ripple-data | 400GB | # Installation @@ -50,7 +50,6 @@ cd /opt/connected-chains ./install.sh mainnet ``` - `` should be at least 64 characters long. To generate passwords for testnets, run with `testnet` as first parameter: `./install.sh testnet ` @@ -58,17 +57,20 @@ To generate passwords for testnets, run with `testnet` as first parameter: `./in # Running All containers: + ``` cd /opt/connected-chains docker compose up -d ``` Single container: + ``` docker compose up -d bitcoin ``` Stop a single container: + ``` docker compose stop bitcoin ``` @@ -80,6 +82,7 @@ You can check the bootstrap process with the `hc.sh` script. `./hc ` (suffixes and prefix 'v' allowed) in Dockerfile. +Commits to main with changes to `images/debug/**` context will automatically trigger a rebuild and push of image, with tag sourced from `ARG VERSION=` (suffixes and prefix 'v' allowed) in Dockerfile. For development purposes, you can also trigger the pipeline with a custom tag like so (the commit still needs to have made changes to `images/debug/**` context): @@ -128,11 +132,14 @@ docker compose logs -f --tail=1000 bitcoin ``` # Node configuration + Each node has a configuration file provided in `/opt/connected-chains//.conf`. Config files are volume-mounted to each container. After changing the config file you should restart the container. # Node data + Data for each node is stored in a docker volume. To find the exact mount point on your filesystem, run + ``` docker volume ls sudo docker inspect | grep Mountpoint @@ -141,17 +148,20 @@ sudo docker inspect | grep Mountpoint # Running testnets All containers: + ``` cd /opt/connected-chains docker compose -f docker-compose-testnet.yml up -d ``` Single container: + ``` docker compose -f docker-compose-testnet.yml up -d bitcoin ``` Stop a single container: + ``` docker compose -f docker-compose-testnet.yml stop bitcoin ``` @@ -161,6 +171,7 @@ You can check the bootstrap process with the `hc-testnet.sh` script. `./hc-testn Configs are loaded from `config-testnet` directory. # Security considerations + Installation script will create a username and password for nodes and insert a line into each config file. You should save the usernames and passwords in your password manager for later use. You can always change them by editing the config files manually. @@ -170,10 +181,31 @@ If you are running attestation clients on a different machine, consider locking If you are running the clients in different networks you might also want to consider running TLS, either natively on each node that supports it or behind a reverse proxy. -# Mounted storage +# Container image + +Public container images are hosted on Github Packages; + +ghcr.io/flare-foundation/connected-chains-docker/bitcoind +ghcr.io/flare-foundation/connected-chains-docker/litecoind +ghcr.io/flare-foundation/connected-chains-docker/dogecoind +ghcr.io/flare-foundation/connected-chains-docker/rippled +ghcr.io/flare-foundation/connected-chains-docker/algorand + +Images are signed using Cosign with the GitHub OIDC provider. To verify the image, run this command: + +``` +cosign verify \ + --certificate-identity-regexp="^https://github\.com/flare-foundation/connected-chains-docker/\.github/workflows/release-.*\.yml@" \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + ghcr.io/flare-foundation/connected-chains-docker/: +``` + +# Mounted storage + If you mount additional storage and want it to be used by the chains, change docker data to your mounted directory: In `/etc/docker/daemon.json` add/change: + ``` { "data-root": "/data" @@ -185,6 +217,7 @@ followed by `sudo systemctl restart docker`. Alternatively, if you do not wish to change data directory for your docker daemon you can switch to bind volume mounts or volume mounts with nfs driver in the compose file. # Algorand fast sync + Algorand node supports syncing just the latest blocks in the blockchain, but it downloads full history by default. This can take up to 14 days. If you don't want to download the whole blockchain, you can run the script `algorand-catchup.sh` after the node has started bootstrapping. This will use Algorand fast catchup feature to automatically catchup to the latest catchpoint. After starting the catchup, the node should finish bootstrapping in few hours. @@ -194,6 +227,7 @@ This will use Algorand fast catchup feature to automatically catchup to the late All Dockerfile definitions are in `images` folder. Images use Moby BuildKit extensions. Common build problems: + - OOM kills compiler. Increase memory of your docker daemon (if using Docker desktop), increase Docker daemon swap and lower the parallel jobs flag (the `-j X` parameter). - if compiler is killed by OOM, image can continue building as if nothing bad happened. Makes sure to check the logs to confirm that build process succeeded. Otherwise container won't start due to missing binaries. - git clone hangs: try to increase the setting `git config --global http.postBuffer ` @@ -205,12 +239,14 @@ If you're upgrading from older versions of this repository, you'll need to updat ## Volumes Find volume locations on host: + ``` docker volume ls docker inspect | grep Mountpoint ``` For every volume, recursively change its ownership to `65532:65532`: + ``` sudo chown -R 65532:65532 ``` @@ -218,6 +254,7 @@ sudo chown -R 65532:65532 ## Bind mounts Recursively change ownership of directory on host to `65532:65532`: + ``` sudo chown -R 65532:65532 ``` diff --git a/images/bitcoind/Dockerfile b/images/bitcoind/Dockerfile index c1cd4d0..3ba7b09 100644 --- a/images/bitcoind/Dockerfile +++ b/images/bitcoind/Dockerfile @@ -16,6 +16,7 @@ RUN <<-EOF curl EOF + RUN git clone --single-branch --branch "${VERSION}" https://github.com/bitcoin/bitcoin.git /opt/bitcoin WORKDIR /opt/bitcoin @@ -26,7 +27,7 @@ RUN <<-EOF cmake -B build \ --toolchain /opt/bitcoin/depends/x86_64-pc-linux-gnu/toolchain.cmake \ -DCMAKE_INSTALL_PREFIX=/opt/bitcoin/build/ - cmake --build build -j $(getconf _NPROCESSORS_ONLN) + cmake --build build -j $(getconf _NPROCESSORS_ONLN) cmake --install build find /opt/bitcoin/build/bin -type f -executable -exec strip -s {} + 2>/dev/null || true EOF diff --git a/images/rippled/Dockerfile b/images/rippled/Dockerfile index c5cfbd4..0bd7617 100644 --- a/images/rippled/Dockerfile +++ b/images/rippled/Dockerfile @@ -34,7 +34,7 @@ ENV PATH="/opt/conan_env/bin:$PATH" RUN <<-EOF set -e git config --global http.postBuffer 1048576000 - conan config install /opt/ripple/conan/profiles/default -tf $(conan config home)/profiles + conan config install /opt/ripple/conan/profiles/default -tf $(conan config home)/profiles conan remote add --index 0 xrplf "https://conan.ripplex.io" mkdir cmake_build EOF