diff --git a/.gitignore b/.gitignore index 8c6b6ef..1fe81be 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ # Guard against accidental commits of the `local` and `tmp` paths /local /tmp + +#StageX build directory +/build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c725f2b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,134 @@ +# syntax=docker/dockerfile:1 + +# stages: +# - setup: sets default values +# - release: builds release binary +# - export: minimal binary export +# - runtime: prepares the release image +# +# We first set default values for build arguments used across the stages. +# Each stage must define the build arguments (ARGs) it uses. + +ARG RUST_VERSION=1.91.1 + +ARG FEATURES="" + +ARG UID=10801 +ARG GID=${UID} +ARG USER="user" +ARG HOME="/home/${USER}" +ARG CARGO_HOME="${HOME}/.cargo" +ARG CARGO_TARGET_DIR="${HOME}/target" +ARG TARGET_ARCH="x86_64-unknown-linux-musl" + +FROM stagex/core-busybox@sha256:d608daa946e4799cf28b105aba461db00187657bd55ea7c2935ff11dac237e27 AS busybox +FROM stagex/pallet-rust@sha256:4062550919db682ebaeea07661551b5b89b3921e3f3a2b0bc665ddea7f6af1ca AS pallet-rust + +# This stage captures build args as env vars +FROM pallet-rust AS setup + +SHELL ["/bin/sh", "-xo", "pipefail", "-c"] + +# Build arguments and variables +ARG CARGO_INCREMENTAL +# default to 0, disables incremental compilation. +ENV CARGO_INCREMENTAL=${CARGO_INCREMENTAL:-0} + +ARG CARGO_HOME +ENV CARGO_HOME=${CARGO_HOME} + +# This stage builds the zcash-devtool release binary. +FROM setup AS release + +ARG HOME +WORKDIR ${HOME} + +ARG CARGO_HOME +ARG CARGO_TARGET_DIR +ARG TARGET_ARCH + +ENV RUST_BACKTRACE=1 +ENV RUSTFLAGS="-C codegen-units=1" +ENV RUSTFLAGS="${RUSTFLAGS} -C target-feature=+crt-static" +ENV RUSTFLAGS="${RUSTFLAGS} -C link-arg=-Wl,--build-id=none" + +ENV SOURCE_DATE_EPOCH=1 +ENV CXXFLAGS="-include cstdint" + +COPY . . + +RUN --mount=type=bind,source=Cargo.toml,target=Cargo.toml,ro \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock,ro \ + --mount=type=cache,target=${HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ + --mount=type=cache,target=${CARGO_TARGET_DIR} \ + --mount=type=cache,target=${CARGO_HOME} \ + cargo fetch --locked --target $TARGET_ARCH && \ + cargo metadata --locked --format-version=1 > /dev/null 2>&1 + +RUN --network=none \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml,ro \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock,ro \ + --mount=type=cache,target=${HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ + --mount=type=cache,target=${CARGO_TARGET_DIR} \ + --mount=type=cache,target=${CARGO_HOME} \ + cargo build --frozen --release --all-features --target ${TARGET_ARCH} && \ + install -D -m 0755 ${HOME}/target/${TARGET_ARCH}/release/zcash-devtool /usr/local/bin/zcash-devtool + +# This stage is used to export the binary +FROM scratch AS export +COPY --from=release /usr/local/bin/* / + +# This stage starts from StageX/busybox and copies the built +# zcash-devtool binary from the `release` stage +FROM busybox AS runtime + +ARG FEATURES +ENV FEATURES=${FEATURES} + +# Create a non-privileged user for running `zcash-devtool`. +# +# We use a high UID/GID (10801) to avoid overlap with host system users. +# This reduces the risk of container user namespace conflicts with host accounts, +# which could potentially lead to privilege escalation if a container escape occurs. +# +# We do not use the `--system` flag for user creation since: +# 1. System user ranges (100-999) can collide with host system users +# (see: https://github.com/nginxinc/docker-nginx/issues/490) +# 2. There's no value added and warning messages can be raised at build time +# (see: https://github.com/dotnet/dotnet-docker/issues/4624) +# +# The high UID/GID values provide an additional security boundary in containers +# where user namespaces are shared with the host. +ARG UID +ENV UID=${UID} +ARG GID +ENV GID=${GID} +ARG USER +ENV USER=${USER} +ARG HOME +ENV HOME=${HOME} + +COPY --chmod=550 <<-EOF /etc/passwd + root:x:0:0:root:/root:/bin/sh + user:x:${UID}:${GID}::${HOME}:/bin/sh +EOF + +COPY --chmod=550 <<-EOF /etc/group + root:x:0: + user:x:${GID}: +EOF + +USER ${UID}:${GID} + +WORKDIR /usr/local/bin + +USER root +COPY --from=release /usr/local/bin/zcash-devtool /usr/local/bin/ +COPY ./utils/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN mkdir -p /usr/local/bin/zec_sqlite_wallet && chown -R ${UID}:${GID} /usr/local/bin/ && chmod -R 770 /usr/local/bin/ && chmod 550 /usr/local/bin/zcash-devtool +USER $USER + +ENTRYPOINT [ "entrypoint.sh" ] +CMD [ "./zcash-devtool" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..836d15e --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# Simple wrapper for scripts with printed status messages. +# +# Running `make` or `make stagex` will leverage the steps below +# to check compatibility and build the binary via StageX. + +.PHONY: stagex compat build load create + +stagex: compat build + @echo "stagex build completed via make." + +compat: + @echo "Beginning Compatibility Check step." + @./utils/compat.sh + @echo " [PASS] Compatibility Check passed." + +build: + @echo "Entering Build step." + @./utils/build.sh + @echo "Build step complete." + +load: + @echo "Attempting to load OCI image into local docker image store." + @./utils/load_image.sh + @echo "make load step complete." + +create: + @echo "Making zcash-devtool wallet. This requires user input. The docker container's runtime shares the host kernel's entropy source." + @./utils/create_wallet.sh + @echo "Wallet creation script complete." diff --git a/README.md b/README.md index 6a6bdab..49f0627 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,13 @@ scale experimentation, at your own risk. ## Usage -No binary artifacts are provided for this crate; it is generally used via -`cargo run` as follows: +No binary artifacts are directly provided for this crate. + +However, a bootstrapped and reproducible build pipeline using StageX is included. +To create a binary, you can simply run `make` in the root directory of the repo. +The resulting binary will be found in the `/build/` directory. + +It can also be used via `cargo run` as follows: To obtain the help docs: ``` diff --git a/utils/build.sh b/utils/build.sh new file mode 100755 index 0000000..5452e23 --- /dev/null +++ b/utils/build.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +DIR="$( cd "$( dirname "$0" )" && pwd )" +REPO_ROOT="$(git rev-parse --show-toplevel)" +PLATFORM="linux/amd64" +OCI_OUTPUT="$REPO_ROOT/build/oci" +DOCKERFILE="$REPO_ROOT/Dockerfile" +NAME=zcash-devtool + +export DOCKER_BUILDKIT=1 +export SOURCE_DATE_EPOCH=1 + +echo $DOCKERFILE +mkdir -p $OCI_OUTPUT + +# Build runtime image for docker run +echo "Building runtime image..." +docker build -f "$DOCKERFILE" "$REPO_ROOT" \ + --platform "$PLATFORM" \ + --target runtime \ + --output type=oci,rewrite-timestamp=true,force-compression=true,dest=$OCI_OUTPUT/zcash-devtool.tar,name=zcash-devtool \ + "$@" + +# Extract from export stage +echo "Extracting binaries..." +docker build -f "$DOCKERFILE" "$REPO_ROOT" --quiet \ + --platform "$PLATFORM" \ + --target export \ + --output type=local,dest="$REPO_ROOT/build" \ + "$@" diff --git a/utils/compat.sh b/utils/compat.sh new file mode 100755 index 0000000..f2ad69d --- /dev/null +++ b/utils/compat.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -e +readonly MIN_BASH_VERSION=5 +readonly MIN_DOCKER_VERSION=26.0.0 +readonly MIN_BUILDX_VERSION=0.13 +### Exit with error message +die() { + echo "$@" >&2 + exit 1 +} + +### Bail and instruct user on missing package to install for their platform +die_pkg() { + local -r package=${1?} + local -r version=${2?} + local install_cmd + case "$OSTYPE" in + linux*) + if command -v "apt" >/dev/null; then + install_cmd="apt install ${package}" + elif command -v "yum" >/dev/null; then + install_cmd="yum install ${package}" + elif command -v "pacman" >/dev/null; then + install_cmd="pacman -Ss ${package}" + elif command -v "emerge" >/dev/null; then + install_cmd="emerge ${package}" + elif command -v "nix-env" >/dev/null; then + install_cmd="nix-env -i ${package}" + fi + ;; + bsd*) install_cmd="pkg install ${package}" ;; + darwin*) install_cmd="port install ${package}" ;; + *) die "Error: Your operating system is not supported" ;; + esac + echo "Error: ${package} ${version}+ does not appear to be installed." >&2 + [ -n "$install_cmd" ] && echo "Try: \`${install_cmd}\`" >&2 + exit 1 +} + +### Check if actual binary version is >= minimum version +check_version(){ + local pkg="${1?}" + local have="${2?}" + local need="${3?}" + local i ver1 ver2 IFS='.' + [[ "$have" == "$need" ]] && return 0 + read -r -a ver1 <<< "$have" + read -r -a ver2 <<< "$need" + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); + do ver1[i]=0; + done + for ((i=0; i<${#ver1[@]}; i++)); do + [[ -z ${ver2[i]} ]] && ver2[i]=0 + ((10#${ver1[i]} > 10#${ver2[i]})) && return 0 + ((10#${ver1[i]} < 10#${ver2[i]})) && die_pkg "${pkg}" "${need}" + done +} + +### Check if required binaries are installed at appropriate versions +check_tools(){ + if [ -z "${BASH_VERSINFO[0]}" ] \ + || [ "${BASH_VERSINFO[0]}" -lt "${MIN_BASH_VERSION}" ]; then + die_pkg "bash" "${MIN_BASH_VERSION}" + fi + for cmd in "$@"; do + case $cmd in + buildx) + docker buildx version >/dev/null 2>&1 || die "Error: buildx not found" + version=$(docker buildx version 2>/dev/null | grep -o 'v[0-9.]*' | sed 's/v//') + check_version "buildx" "${version}" "${MIN_BUILDX_VERSION}" + ;; + docker) + command -v docker >/dev/null || die "Error: docker not found" + version=$(docker version -f '{{ .Server.Version }}') + check_version "docker" "${version}" "${MIN_DOCKER_VERSION}" + ;; + esac + done +} + +check_tools docker buildx; +docker info -f '{{ .DriverStatus }}' \ + | grep "io.containerd.snapshotter.v1" >/dev/null \ +|| die "Error: Docker Engine is not using containerd for image storage" diff --git a/utils/create_wallet.sh b/utils/create_wallet.sh new file mode 100755 index 0000000..56f5687 --- /dev/null +++ b/utils/create_wallet.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +echo "Checking local docker image store to see if a zcash-devtool:latest image is present." +# Checks for empty string, discarding error messages. +if [ -z "$(docker images -q zcash-devtool:latest 2>/dev/null)" ]; then + echo "There is no zcash-devtool:latest image listed by docker." +else + echo "Creating wallet. Connecting via clearnet to zecrocks." + docker run -it zcash-devtool:latest ./zcash-devtool wallet init --name "stagex_container_wallet" --identity ./age_id.txt --connection direct --network test -s zecrocks +fi diff --git a/utils/entrypoint.sh b/utils/entrypoint.sh new file mode 100755 index 0000000..43533b3 --- /dev/null +++ b/utils/entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Entrypoint for running zcash-devtool in Docker. +# +# The main script logic is at the bottom. +# +# ## Notes +# +# zcash-devtool is a stateless tool. Each command has some effect: +# inspect +# Intended to be run against "anything zcash" and provide information. For example, +# an address, or a transaction. +# wallet +# Can create and inspect wallets, sync, send zec and so on. + +set -eo pipefail + +# Main Script Logic +# +# 1. Print environment variables and config for debugging. +# 2. Tests if zcash-devtool runs, printing help. +# 3. Execs the CMD or custom command provided. + +echo "INFO: Using the following environment variables:" +printenv + +echo "Testing zcash-devtool to print version string:" +./zcash-devtool help + +echo "now exec'ing $@ " +exec "$@" diff --git a/utils/load_image.sh b/utils/load_image.sh new file mode 100755 index 0000000..c14b5bc --- /dev/null +++ b/utils/load_image.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +REPO_ROOT="$(git rev-parse --show-toplevel)" +OCI_OUTPUT="$REPO_ROOT/build/oci" +TARBALL="${OCI_OUTPUT}/zcash-devtool.tar" + + +# Build runtime image for docker run +echo "Checking if the OCI output from build is present." +if [ -f "$TARBALL" ]; +then + echo "OCI output file present, loading tar file into local docker image store." + docker load < $TARBALL + echo "...Done!" +else + echo "OCI output file not present." +fi +