diff --git a/.dockerignore b/.dockerignore index cede6cc78..7739a9d3d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,6 @@ .git *.egg-info deps -jmvenv \ No newline at end of file +jmvenv +compose.yml +Dockerfile \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 30779464d..dd6138eb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,40 @@ -FROM debian:bookworm-slim - -RUN mkdir -p /jm/clientserver +ARG BITCOIN_VERSION=29.2 +ARG PYTHON_IMAGE_TAG=3.12-slim-trixie +FROM bitcoin/bitcoin:${BITCOIN_VERSION} AS bitcoin +FROM python:${PYTHON_IMAGE_TAG} AS python WORKDIR /jm/clientserver +COPY ./pubkeys ./pubkeys +COPY ./install.sh ./install.sh -COPY . . - -RUN apt-get update && apt-get install -y --no-install-recommends gnupg ca-certificates=* curl=* \ - python3-pip=* python3=* \ - && pip3 config set global.break-system-packages true \ - && pip3 install 'wheel>=0.35.1' \ - && ./install.sh --docker-install \ - && apt-get purge -y --autoremove python3-pip \ +FROM python AS base-deps +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update \ + && apt-get install -y --no-install-recommends \ + libsecp256k1-2 \ + libsodium23 \ + openssl \ + tor \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +FROM base-deps AS builder +COPY . . +RUN python -m venv jmvenv \ + && . ./jmvenv/bin/activate \ + && pip install .[services] + +FROM base-deps AS base +COPY --from=builder /jm/clientserver /jm/clientserver +ENTRYPOINT ["./scripts/docker-entrypoint.sh"] + +FROM base AS test +ARG BITCOIN_VERSION +COPY --from=bitcoin /opt/bitcoin-${BITCOIN_VERSION}/bin /usr/local/bin/ +RUN . ./jmvenv/bin/activate \ + && pip install .[test] + +FROM base AS obwatcher +RUN . ./jmvenv/bin/activate && pip install matplotlib +CMD ["python", "./scripts/obwatch/ob-watcher.py"] + +FROM base AS final diff --git a/README.md b/README.md index b50a8de2c..f2554f67a 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,18 @@ Alternative to this "quickstart": follow the [install guide](docs/INSTALL.md). * [Installation guide for Qubes+Whonix](https://github.com/qubenix/qubes-whonix-bitcoin/blob/master/1_joinmarket.md). * [Youtube video installation tutorial for Ubuntu](https://www.youtube.com/watch?v=zTCC86IUzWo). +### Docker + +JoinMarket can be built and run using Docker. To build the Docker image: + + docker build -t joinmarket . + +To run tests using Docker Compose: + + docker compose up test + +This will run the full test suite in an isolated environment with all necessary dependencies including Bitcoin Core. + ### Usage If you are new, follow and read the links in the [usage guide](docs/USAGE.md). diff --git a/compose.yml b/compose.yml new file mode 100644 index 000000000..f4d3b9b72 --- /dev/null +++ b/compose.yml @@ -0,0 +1,21 @@ +services: + test: + image: joinmarket:test + build: + context: . + target: test + args: &build_args + BITCOIN_VERSION: 29.2 + PYTHON_IMAGE_TAG: 3.12-slim-trixie + volumes: + - ./src:/jm/clientserver/src + - ./test:/jm/clientserver/test + shm_size: '2gb' + command: ["./test/run_tests.sh", "-v"] + obwatcher: + image: joinmarket:obwatcher + build: + context: . + target: obwatcher + args: *build_args + command: ["python", "./scripts/obwatch/ob-watcher.py"] \ No newline at end of file diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ebcb294b6..7875f43ed 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -265,14 +265,47 @@ There, you need to install the client code (without Joinmarket's bitcoin): #### Docker Installation -The [Dockerfile](../Dockerfile) provided builds a minimal Docker image which can help in getting started with a custom Docker setup. An example of building and running the [wallet-tool.py](../scripts/wallet-tool.py) script: +The [Dockerfile](../Dockerfile) provided builds an optimized multi-stage Docker image for JoinMarket. The build process is optimized for layer caching and includes all necessary dependencies. -``` -docker build -t joinmarket-test ./ -docker run --rm -it joinmarket-test bash -c "cd scripts && python3 wallet-tool.py --help" -``` +##### Building the Docker image + +To build the production image: + + docker build -t joinmarket . + +This creates a minimal production image with JoinMarket installed. + +##### Running JoinMarket scripts in Docker + +Example of running the [wallet-tool.py](../scripts/wallet-tool.py) script: + + docker run --rm -it joinmarket bash -c "source jmvenv/bin/activate && cd scripts && python wallet-tool.py --help" + +##### Running tests with Docker Compose + +The repository includes a `compose.yml` file for easy testing: + + docker compose up test + +This will: + +* Build a test image that includes Bitcoin Core 29.2 +* Mount the `src` and `test` directories for development +* Run the full test suite with all dependencies configured automatically +* Use shared memory for improved test performance + +##### Building custom images + +The Dockerfile uses multi-stage builds with the following targets: + +* `joinmarket` (default) - Production image with JoinMarket installed +* `test` - Testing image that includes Bitcoin Core binaries + +You can build a specific target: + + docker build --target test -t joinmarket:test . -A new Docker image can be built using `joinmarket-test` as a base using `FROM joinmarket-test`. See [Docker documentation](https://docs.docker.com/engine/reference/builder/) for more details. +See [Docker documentation](https://docs.docker.com/engine/reference/builder/) for more details on multi-stage builds. #### Development (or making other changes to the code) diff --git a/docs/TESTING.md b/docs/TESTING.md index 821aa4249..afc1fc5b6 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,5 +1,22 @@ ### Test instructions (for developers): +#### Quick start: Running tests with Docker + +The easiest way to run the full test suite is using Docker Compose, which handles all dependencies automatically: + + docker compose run --rm test + +This will: + +* Build a Docker image with all dependencies (Python, Bitcoin Core 29.2, miniircd) +* Run the complete test suite in an isolated environment +* Automatically download and set up miniircd +* No need to install bitcoind or other dependencies on your host machine + +For development, the Docker setup mounts the `src` and `test` directories, so you can make changes locally and re-run tests without rebuilding the image. + +#### Manual setup + Work in your `jmvenv` virtual environment as for all Joinmarket work. Make sure to have [bitcoind](https://bitcoin.org/en/full-node) 28.1 or newer installed. Also need miniircd installed to the root (i.e. in your `joinmarket-clientserver` directory): (jmvenv)$ cd /path/to/joinmarket-clientserver diff --git a/install.sh b/install.sh index 2e530819a..4befbcc66 100755 --- a/install.sh +++ b/install.sh @@ -417,6 +417,9 @@ CookieAuthentication 1 joinmarket_install () { + if [[ ${without_jm} == 1 ]]; then + return 0 + fi reqs='services' if [[ ${with_qt} == "1" ]]; then @@ -426,8 +429,7 @@ joinmarket_install () reqs+=',test' fi - if [ "$with_jmvenv" == 1 ]; then pip_command=pip; else pip_command=pip3; fi - $pip_command install -e ".[${reqs}]" || return 1 + pip install -e ".[${reqs}]" || return 1 if [[ ${with_qt} == "1" ]]; then if [[ -d ~/.local/share/icons ]] && [[ -d ~/.local/share/applications ]]; then @@ -486,7 +488,9 @@ parse_flags () ;; --docker-install) with_sudo='0' - with_jmvenv='0' + ;; + --deps) + without_jm='1' ;; "") break @@ -509,6 +513,7 @@ Options: --with-local-tor build Tor locally and autostart when needed --with-qt build the Qt GUI --without-qt don't build the Qt GUI +--deps install dependencies only, do not build or install Joinmarket " return 1 ;; @@ -570,22 +575,18 @@ main () use_os_deps_check='1' use_secp_check='1' with_qt='' - with_jmvenv='1' with_sudo='1' + without_jm='0' reinstall='false' if ! parse_flags "${@}"; then return 1 fi jm_source="$PWD" - if [ "$with_jmvenv" == 1 ]; then - jm_root="${jm_source}/jmvenv" - export PKG_CONFIG_PATH="${jm_root}/lib/pkgconfig:${PKG_CONFIG_PATH}" - export LD_LIBRARY_PATH="${jm_root}/lib:${LD_LIBRARY_PATH}" - export C_INCLUDE_PATH="${jm_root}/include:${C_INCLUDE_PATH}" - else - jm_root="" - fi + jm_root="${jm_source}/jmvenv" + export PKG_CONFIG_PATH="${jm_root}/lib/pkgconfig:${PKG_CONFIG_PATH}" + export LD_LIBRARY_PATH="${jm_root}/lib:${LD_LIBRARY_PATH}" + export C_INCLUDE_PATH="${jm_root}/include:${C_INCLUDE_PATH}" # os check install_os="$( install_get_os )" @@ -597,16 +598,12 @@ main () MAKEFLAGS="-j $(num_cores)" && export MAKEFLAGS - if [ "$with_jmvenv" == 1 ]; then - if ! venv_setup; then - echo "Joinmarket Python virtual environment could not be setup. Exiting." - return 1 - fi - # shellcheck source=/dev/null - source "${jm_root}/bin/activate" - else - upgrade_setuptools + if ! venv_setup; then + echo "Joinmarket Python virtual environment could not be setup. Exiting." + return 1 fi + # shellcheck source=/dev/null + source "${jm_root}/bin/activate" if [[ ${build_local_tor} == "1" ]]; then if ! tor_deps_install; then echo "Tor dependencies could not be installed. Exiting." @@ -638,17 +635,15 @@ main () popd || return 1 if ! joinmarket_install; then echo "Joinmarket was not installed. Exiting." - if [ "$with_jmvenv" == 1 ]; then deactivate; fi + deactivate return 1 fi - if [ "$with_jmvenv" == 1 ]; then - deactivate - echo "Joinmarket successfully installed - Before executing scripts or tests, run: + deactivate + echo "Joinmarket successfully installed + Before executing scripts or tests, run: - \`source jmvenv/bin/activate\` + \`source jmvenv/bin/activate\` - from this directory, to activate the virtual environment." - fi + from this directory, to activate the virtual environment." } main "${@}" diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100755 index 000000000..f1c202725 --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# shellcheck disable=SC1091 +source jmvenv/bin/activate +exec "$@" \ No newline at end of file