Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
dcff36a
cargo update
ala-mode Feb 1, 2026
2a4fa5c
add stagex util scripts
ala-mode Feb 1, 2026
886bbe1
add stagex Dockerfile
ala-mode Feb 1, 2026
3f9bc84
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 2, 2026
120d83a
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 3, 2026
1f39bce
cleanup
ala-mode Feb 13, 2026
d0c7e9b
update README
ala-mode Feb 13, 2026
065ad85
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 13, 2026
03aae29
add Makefile
ala-mode Feb 13, 2026
b28552a
remove duplicate dependency
ala-mode Feb 13, 2026
55a7aeb
cargo update
ala-mode Feb 13, 2026
4507a24
busybox runtime, network-patch for build script, force static linking…
ala-mode Feb 13, 2026
fdc0f7f
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 13, 2026
d33203d
fix spelling in README
ala-mode Feb 15, 2026
a4993db
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 16, 2026
8413b7f
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 16, 2026
6beb9fc
add ENTRYPOINT
ala-mode Feb 16, 2026
30dfa55
point ENTRYPOINT
ala-mode Feb 16, 2026
757e5b9
add entrypoint.sh
ala-mode Feb 17, 2026
e3886b4
copy entrypoint.sh from builder stage, exec mode CMD
ala-mode Feb 17, 2026
c8b4e8d
etc/ permissions, higher UID, standardize runtime dirs, USER root, co…
ala-mode Feb 17, 2026
3b8d6dd
WIP, setpriv, dir ownership, create wallet dir
ala-mode Feb 17, 2026
4078295
WIP, util-linux, setpriv
ala-mode Feb 18, 2026
c19186b
WIP, working Dockerfile, no entrypoint use
ala-mode Feb 21, 2026
2a7750f
restrict permissions, change back to use entrypoint.sh
ala-mode Feb 23, 2026
d31a00d
entrypoint.sh overhaul: simple sync, version pint, info, tail for per…
ala-mode Feb 23, 2026
2dec19e
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 23, 2026
503d9e1
update Makefile
ala-mode Feb 24, 2026
1fcb81f
add create_wallet.sh
ala-mode Feb 24, 2026
1193c34
update entrypoint.sh
ala-mode Feb 24, 2026
853cd17
update entrypoint.sh with if ./initialized
ala-mode Feb 24, 2026
17e3a47
cleanup
ala-mode Feb 24, 2026
732854e
tune ENTRYPOINT, tighten permissions, cleanup
ala-mode Feb 24, 2026
26c4d76
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 24, 2026
66cc448
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 25, 2026
9347d26
update entrypoint.sh and create_wallet
ala-mode Feb 25, 2026
7131ec3
add interact.sh and load_image.sh
ala-mode Feb 25, 2026
f98ae9e
update Makefile with load, interact
ala-mode Feb 25, 2026
51a3d14
good docker run in interact.sh
ala-mode Feb 25, 2026
2a3f426
cleanup Dockerfile
ala-mode Feb 25, 2026
6317011
cleanup
ala-mode Feb 26, 2026
6f86136
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 26, 2026
a195817
correct load_image.sh
ala-mode Feb 26, 2026
17492b2
cleanup create_wallet.sh
ala-mode Feb 26, 2026
c69fce8
Merge branch 'dev' into stagex_reproducable_build
ala-mode Feb 27, 2026
e4377f0
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 2, 2026
3552ba1
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 9, 2026
964c5fe
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 13, 2026
d06126a
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 26, 2026
cadf0c5
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 26, 2026
5c8375d
Merge branch 'dev' into stagex_reproducable_build
ala-mode Mar 28, 2026
ea907d2
readd --quiet
ala-mode Mar 28, 2026
00fcb0c
update README
ala-mode Mar 28, 2026
fcacead
CMD defaults to simple --help
ala-mode Mar 28, 2026
2ab4171
expand README
ala-mode Mar 28, 2026
5657724
rm create and interact from Makefile
ala-mode Mar 28, 2026
41a5524
rm scripts for create wallet and interact
ala-mode Mar 28, 2026
fe29200
mv notes to README
ala-mode Mar 28, 2026
98f9b02
update StageX images, WIP ARGs etc
ala-mode Mar 28, 2026
e361630
mount cache paths with env vars
ala-mode Mar 28, 2026
fcbf14c
set workdir with ARG
ala-mode Mar 29, 2026
d112222
WIP release stage ARGs and ENVs
ala-mode Mar 29, 2026
1c3e5e0
complete ARGs and ENVs in release stage, rm cargo metadata, begin cac…
ala-mode Mar 29, 2026
8ff49f4
WIP transition to bind mounts
ala-mode Mar 29, 2026
d5ba6c3
set bind paths to project root, working hacks for params build script
ala-mode Mar 31, 2026
8850f20
reset cargo install path for binary
ala-mode Mar 31, 2026
a1a4030
add optional FEATURES to build, add GH issue #s, cleanup
ala-mode Mar 31, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.helix
build/
cobertura.xml
target
wallets/*
Expand Down
185 changes: 185 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# syntax=docker/dockerfile:1
# check=skip=UndefinedVar,UserExist

# stages:
# - release: setup and builds release binaries
# - export: discrete stage for writing binaries into host build directory
# - 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 FEATURES=""

############################
# Global build args
############################
ARG UID=10901
ARG GID=${UID}
ARG USER="user"
ARG HOME="/home/${USER}"
ARG CARGO_HOME="/usr/local/.cargo"
ARG CARGO_TARGET_DIR="${HOME}/target"
ARG TARGET_ARCH="x86_64-unknown-linux-musl"

############################
# Dependencies
############################
# Build Deps
FROM stagex/pallet-rust:1.94.0@sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668 AS pallet-rust
FROM stagex/user-protobuf:26.1@sha256:a135aaf060990b6ef8a7c715c16f175811d3a1f5383970f5771adef05a0bc56a AS protobuf
FROM stagex/user-abseil-cpp:20240116.2@sha256:20a241145158a0aa7cb83ed5dc4f9ad6360dc975352787f4e6b00e8a39943f62 AS abseil-cpp
FROM stagex/core-sqlite3:3.50.1@sha256:8d2959fcde94119a724315d9c9f58acf59c5ae83cf4ad22a36ac1ed971327237 AS sqlite3
# Runtime Deps
FROM stagex/core-busybox:1.37.0@sha256:d608daa946e4799cf28b105aba461db00187657bd55ea7c2935ff11dac237e27 AS busybox


############################
# Release
############################
FROM pallet-rust AS release
COPY --from=protobuf . /
COPY --from=abseil-cpp . /
COPY --from=sqlite3 . /

SHELL ["/bin/sh", "-euo", "pipefail", "-c"]

ARG HOME
WORKDIR ${HOME}

ARG CARGO_INCREMENTAL
# default to 0, disables incremental compilation.
ENV CARGO_INCREMENTAL=${CARGO_INCREMENTAL:-0}

ARG CARGO_HOME
ENV CARGO_HOME=${CARGO_HOME}

ARG CARGO_TARGET_DIR
ARG TARGET_ARCH

ARG FEATURES
ENV FEATURES=${FEATURES}

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

# TODO : restore:
# cargo fetch --locked --target $TARGET_ARCH
# --locked was removed due to consistant breakage between Cargo.Toml and Cargo.lock
# see Github issues #2114 and #2311

RUN --mount=type=cache,target=${CARGO_HOME}/registry \
--mount=type=cache,target=${CARGO_HOME}/git \
--mount=type=bind,source=rust-toolchain.toml,target=rust-toolchain.toml,ro \
--mount=type=bind,source=Cargo.toml,target=Cargo.toml,ro \
--mount=type=bind,source=Cargo.lock,target=Cargo.lock,ro \
--mount=type=bind,source=/darkside-tests,target=darkside-tests,ro \
--mount=type=bind,source=/libtonode-tests,target=libtonode-tests,ro \
--mount=type=bind,source=/pepper-sync,target=pepper-sync,ro \
--mount=type=bind,source=/zingo-cli,target=zingo-cli,ro \
--mount=type=bind,source=/zingolib,target=zingolib,ro \
--mount=type=bind,source=/zingo-memo,target=zingo-memo,ro \
--mount=type=bind,source=/zingo-price,target=zingo-price,ro \
--mount=type=bind,source=/zingo-status,target=zingo-status,ro \
--mount=type=bind,source=/zingolib_testutils,target=zingolib_testutils,ro \
cargo fetch --target $TARGET_ARCH

# TODO : --network=none was removed due to network requests in build script
# (docker level network denial)
# and cargo build requiring network access as well (see Github issue #2162)
# this needs to be re-added to ensure hermeticity
#
# TODO: additionally, restore
# cargo build --release --frozen --target $TARGET_ARCH --bin zingo-cli && install -D -m 0755 target/${TARGET_ARCH}/release/zingo-cli /usr/local/bin/zingo-cli
# --frozen was als removed due to build script

# TODO : get rid of:
# --mount=type=cache,target=${HOME}/.zcash-params \
# See Github issue #2314
# this works!
# TODO : get rid of:
# --mount=type=cache,target=zingolib/zcash-params \
# This soothes the savage beast as well!
# see Github issue #2315

RUN --mount=type=cache,target=${CARGO_HOME}/registry \
--mount=type=cache,target=${CARGO_HOME}/git \
--mount=type=cache,target=${HOME}/target \
--mount=type=cache,target=${HOME}/.zcash-params \
--mount=type=cache,target=zingolib/zcash-params \
--mount=type=bind,source=rust-toolchain.toml,target=rust-toolchain.toml,ro \
--mount=type=bind,source=Cargo.toml,target=Cargo.toml,ro \
--mount=type=bind,source=Cargo.lock,target=Cargo.lock,ro \
--mount=type=bind,source=/darkside-tests,target=darkside-tests,ro \
--mount=type=bind,source=/libtonode-tests,target=libtonode-tests,ro \
--mount=type=bind,source=/pepper-sync,target=pepper-sync,ro \
--mount=type=bind,source=/zingo-cli,target=zingo-cli,ro \
--mount=type=bind,source=/zingolib,target=zingolib,ro \
--mount=type=bind,source=/zingo-memo,target=zingo-memo,ro \
--mount=type=bind,source=/zingo-price,target=zingo-price,ro \
--mount=type=bind,source=/zingo-status,target=zingo-status,ro \
--mount=type=bind,source=/zingolib_testutils,target=zingolib_testutils,ro \
cargo build --release ${FEATURES:+--features ${FEATURES}} --target $TARGET_ARCH --bin zingo-cli && install -D -m 0755 target/${TARGET_ARCH}/release/zingo-cli /usr/local/bin/zingo-cli

############################
# Export stage
############################
FROM scratch AS export
COPY --from=release /usr/local/bin/zingo-cli /zingo-cli

############################
# Runtime stage
############################
FROM busybox AS runtime

# Create a non-privileged user for running `zingo-cli`.
#
# We use a high UID/GID (10901) 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

WORKDIR /usr/local/bin

USER root
RUN mkdir -p /usr/local/bin/wallets && chown -R ${UID}:${GID} /usr/local/bin/ && chmod -R 770 /usr/local/bin/
COPY --chown=${UID}:${GID} --from=export /zingo-cli /usr/local/bin/zingo-cli
RUN chmod 550 /usr/local/bin/zingo-cli
COPY --chown=${UID}:${GID} ./utils/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 550 /usr/local/bin/entrypoint.sh

USER $USER
# ./entrypoint.sh runs, then executes CMD (or custom command if provided).
# Prints zingo-cli version, address if a new wallet is created, and info on success.
ENTRYPOINT [ "./entrypoint.sh" ]
CMD [ "./zingo-cli", "--help"]
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Simple wrapper for scripts with printed status messages.
#
# Running `make` or `make stagex` will leverage the steps below
# to check compatibility and build the binaries via StageX.

.PHONY: stagex compat build load create interact

stagex: compat build
@echo "[Stageˣ] 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."
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,29 @@ cargo build --release --package zingo-cli

This will launch the interactive prompt. Type `help` to get a list of commands.

## Reproducible builds via StageX

A bootstrapped and reproducible build pipeline using StageX is included in this repo.
If you meet all the compatibility requirements, to create `zingo-cli`, you can run
`make` in the root directory. The resulting binary will be found in the `/build/`
directory, along with an OCI image in the form of a tar ball.

This image can be loaded into docker with the `make load` convenience script, and
contains the `zingo-cli` binary.

To run interactively with a custom server:
`docker run -it zingo-cli:latest ./zingo-cli --server https://zzz.stripest.online:443`

`zingo-cli` runs with several defaults. Importantly, these include a data-dir
with wallet file, which are created if they don't already exist:
a `wallets` dir in location where executable is run, containing the wallet
(`zingo-wallet.dat`) file. Other defaults inlcude setting the chain to mainnet,
using a default lightwallet server, using clearnet for price fetching, and not
executing commands prior to a complete chain sync.

Any `docker run` will initialize a wallet if there was none in the container, and
by default prints info and then help if no arguments are passed.

## Notes:
* If you want to run your own server, please see [zingo lightwalletd](https://github.com/zingolabs/lightwalletd), and then run `./zingo-cli --server http://127.0.0.1:9067`
* The default log file is in `~/.zcash/zingo-wallet.debug.log`. A default wallet is stored in `~/.zcash/zingo-wallet.dat`
Expand Down
31 changes: 31 additions & 0 deletions utils/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/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"

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/zingo-cli.tar,name=zingo-cli \
"$@"

# Extract binary locally from export stage
echo "Extracting binary..."
docker build -f "$DOCKERFILE" "$REPO_ROOT" --quiet \
--platform "$PLATFORM" \
--target export \
--output type=local,dest="$REPO_ROOT/build" \
"$@"
80 changes: 80 additions & 0 deletions utils/compat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/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"
37 changes: 37 additions & 0 deletions utils/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh

# Entrypoint for running zingo-cli in Docker.
#
# The main script logic is at the bottom.

set -eo pipefail

# Currently there is no support for running tests in-container, due to
# requiring additional binaries.
#
# Main Script Logic
#
# 1. Print environment variables and config for debugging.
# 2. Tests if zingo-cli runs.
# 3. Creates a wallet, if the container has not been initialized before.
# 4. Process command-line arguments and execute appropriate action.

echo "INFO: Using the following environment variables:"
printenv

echo "Testing zingo-cli to print version string:"
./zingo-cli --version

if [ ! -f ./initialized ]; then
# A wallet will be created in this container if there is none. The address of the new wallet will be printed after sync."
echo "Container not initialized, creating wallet, syncing, and printing address..."
# selected server = zebra 4.1.0 and lwd v0.4.18-9-gb932e8e at time of commit
./zingo-cli --server https://zzz.stripest.online:443 --waitsync addresses
touch ./initialized
fi

echo "Lightwalletd server's info info:"
./zingo-cli --server https://zzz.stripest.online:443 --nosync info

echo "now exec'ing $@ "
exec "$@"
Loading