diff --git a/.gitignore b/.gitignore index 592f8877e..ac6401e53 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ container-target/ .local/ .failed-tests **/proptest-regressions/** +.zainod_config/ diff --git a/.zainod_config/gitinclude b/.zainod_config/gitinclude new file mode 100644 index 000000000..e69de29bb diff --git a/Dockerfile b/Containerfile.build similarity index 62% rename from Dockerfile rename to Containerfile.build index c54b2a50f..32537f59a 100644 --- a/Dockerfile +++ b/Containerfile.build @@ -1,17 +1,10 @@ # syntax=docker/dockerfile:1 -############################ -# Global build args -############################ -ARG RUST_VERSION=1.86.0 -ARG UID=1000 -ARG GID=1000 -ARG USER=container_user -ARG HOME=/home/container_user - ############################ # Builder ############################ +ARG RUST_VERSION + FROM rust:${RUST_VERSION}-bookworm AS builder SHELL ["/bin/bash", "-euo", "pipefail", "-c"] WORKDIR /app @@ -40,43 +33,19 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ fi ############################ -# Runtime (slim, non-root) +# Runtime ############################ FROM debian:bookworm-slim AS runtime -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ARG UID -ARG GID -ARG USER -ARG HOME - -# Only the dynamic libs needed by a Rust/OpenSSL binary +# Runtime deps RUN apt-get -qq update && \ apt-get -qq install -y --no-install-recommends \ ca-certificates libssl3 libgcc-s1 \ && rm -rf /var/lib/apt/lists/* -# Create non-root user -RUN addgroup --gid "${GID}" "${USER}" && \ - adduser --uid "${UID}" --gid "${GID}" --home "${HOME}" \ - --disabled-password --gecos "" "${USER}" - -WORKDIR ${HOME} - -# Copy the installed binary from builder COPY --from=builder /out/bin/zainod /usr/local/bin/zainod -RUN mkdir -p .cache/zaino -RUN chown -R "${UID}:${GID}" "${HOME}" -USER ${USER} +EXPOSE 8137 8237 -# Default ports (adjust if your app uses different ones) -ARG ZAINO_GRPC_PORT=8137 -ARG ZAINO_JSON_RPC_PORT=8237 -EXPOSE ${ZAINO_GRPC_PORT} ${ZAINO_JSON_RPC_PORT} - -# Healthcheck that doesn't assume specific HTTP/gRPC endpoints HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD /usr/local/bin/zainod --version >/dev/null 2>&1 || exit 1 - -CMD ["zainod"] diff --git a/Containerfile.tail b/Containerfile.tail new file mode 100644 index 000000000..13eebbc3d --- /dev/null +++ b/Containerfile.tail @@ -0,0 +1,12 @@ +# syntax=docker/dockerfile:1 + +ARG RUNTIME_IMAGE +FROM ${RUNTIME_IMAGE} + +# Writable home for XDG defaults (config, cache, etc.) +# The actual UID is mapped at runtime via --userns=keep-id or --user. +RUN mkdir -p /home/zaino && chmod 777 /home/zaino +ENV HOME=/home/zaino + +ENTRYPOINT ["zainod"] +CMD ["start"] diff --git a/Dockerfile.tail b/Dockerfile.tail new file mode 100644 index 000000000..4cb043757 --- /dev/null +++ b/Dockerfile.tail @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +ARG RUNTIME_IMAGE +FROM ${RUNTIME_IMAGE} + +# Docker-specific: baked-in non-root user +ARG UID=1000 +ARG GID=1000 +ARG USER=container_user +ARG HOME=/home/container_user + +RUN addgroup --gid "${GID}" "${USER}" && \ + adduser --uid "${UID}" --gid "${GID}" --home "${HOME}" \ + --disabled-password --gecos "" "${USER}" + +WORKDIR ${HOME} +RUN mkdir -p .cache/zaino +RUN chown -R "${UID}:${GID}" "${HOME}" +USER ${USER} + +CMD ["zainod"] diff --git a/Makefile.toml b/Makefile.toml index e76055a94..4c511795f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -35,6 +35,8 @@ echo " container-test Run integration tests using the local image" echo " container-test-save-failures Run tests, save failures to .failed-tests" echo " container-test-retry-failures Rerun only the previously failed tests" echo " build-image Build the Docker image with current artifact versions" +echo " build-zainod Build zainod container image (makers build-zainod )" +echo " run-zainod Build, configure, and run zainod (makers run-zainod )" echo " push-image Push the image (used in CI, can be used manually)" echo " compute-image-tag Compute the tag for the Docker image based on versions" echo " get-docker-hash Get DOCKER_DIR_HASH value (hash for the image defining files)" @@ -603,3 +605,100 @@ info "Filter: $FILTER" makers container-test -E "$FILTER" "${@}" ''' script.post = "makers notify" + +# ------------------------------------------------------------------- + +[tasks.base-zainod] +private = true +script_runner = "bash" +script.pre = ''' +set -euo pipefail +source "./utils/helpers.sh" + +validate_engine "${1:-}" +require_clean_worktree +compute_zainod_tags + +TAIL_FILE=$(tail_file_for_engine) +CMD_PREFIX=$(cmd_prefix_for_engine) +RUN_FLAGS=$(run_flags_for_engine) +''' +script.main = "err 'base-zainod: override script.main in the extending task'" + +# ------------------------------------------------------------------- + +[tasks.build-zainod] +description = "Build zainod container image (usage: makers build-zainod )" +extend = "base-zainod" +private = false +script.main = ''' +info "Building zainod runtime image" +info "Engine: $ENGINE" +info "Rust: $RUST_VERSION" +info "Commit: $COMMIT" +info "Tag: $RUNTIME_TAG" + +$ENGINE build -f Containerfile.build \ + --build-arg RUST_VERSION="$RUST_VERSION" \ + -t "$RUNTIME_TAG" \ + . + +info "Building final zainod image" +info "Tail file: $TAIL_FILE" +info "Tag: $ZAINOD_TAG" + +$ENGINE build -f "$TAIL_FILE" \ + --build-arg RUNTIME_IMAGE="$RUNTIME_TAG" \ + -t "$ZAINOD_TAG" \ + . + +info "Done. Run with: makers run-zainod $ENGINE" +''' + +# ------------------------------------------------------------------- + +[tasks.run-zainod] +description = "Build, configure, and run zainod (usage: makers run-zainod )" +extend = "base-zainod" +private = false +script.main = ''' +# Skip build if image already exists +if $ENGINE image inspect "$ZAINOD_TAG" > /dev/null 2>&1; then + info "Image $ZAINOD_TAG already exists — skipping build" +else + info "Image $ZAINOD_TAG not found — building..." + makers build-zainod "$ENGINE" +fi + +CONFIG_DIR=".zainod_config" +CONFIG_FILE="${CONFIG_DIR}/zainod.toml" + +# Generate config if it doesn't exist +if [ ! -f "$CONFIG_FILE" ]; then + info "Generating default config at $CONFIG_FILE" + mkdir -p "$CONFIG_DIR" + $ENGINE run --rm \ + $RUN_FLAGS \ + -v "$PWD/$CONFIG_DIR:/config" \ + "$ZAINOD_TAG" \ + $CMD_PREFIX generate-config -o /config/zainod.toml + + # Patch gRPC listen address for container networking + sed -i 's/listen_address = "127\.0\.0\.1:8137"/listen_address = "0.0.0.0:8137"/' "$CONFIG_FILE" + info "Patched gRPC listen_address to 0.0.0.0:8137 for container networking" + warn "Review $CONFIG_FILE and set validator_settings for your setup" +fi + +info "Starting zainod" +info "Engine: $ENGINE" +info "Image: $ZAINOD_TAG" +info "Config: $CONFIG_FILE" + +$ENGINE run --rm \ + $RUN_FLAGS \ + -p 8137:8137 \ + -p 8237:8237 \ + -v "$PWD/$CONFIG_DIR:/config" \ + "$ZAINOD_TAG" \ + $CMD_PREFIX start -c /config/zainod.toml +''' diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 05dded7f8..73cb934de 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.92" +channel = "stable" components = ["rustfmt", "clippy"] diff --git a/utils/helpers.sh b/utils/helpers.sh index b705c5422..21601be5e 100755 --- a/utils/helpers.sh +++ b/utils/helpers.sh @@ -33,3 +33,68 @@ resolve_build_target() { fi } +# ------- ZAINOD CONTAINER HELPERS ------------ + +# Validate the container engine argument. +# Sets ENGINE as a side-effect. +validate_engine() { + ENGINE="${1:?Usage: makers }" + if [ "$ENGINE" != "podman" ] && [ "$ENGINE" != "docker" ]; then + err "Unknown engine: $ENGINE (use podman or docker)" + exit 1 + fi +} + +# Abort unless the working tree is clean. +# Respects FORCE=true to override. +require_clean_worktree() { + if [ -n "$(git status --porcelain)" ]; then + if [ "${FORCE:-}" = "true" ]; then + warn "Working directory is dirty — proceeding because FORCE=true" + else + err "Working directory is dirty. Commit your changes or set FORCE=true" + exit 1 + fi + fi +} + +# Compute image tags from the current rust toolchain and HEAD commit. +# Exports: RUST_VERSION, COMMIT, RUNTIME_TAG, ZAINOD_TAG +compute_zainod_tags() { + RUST_VERSION=$(rustc --version | awk '{print $2}') + COMMIT=$(git rev-parse --short HEAD) + RUNTIME_TAG="zaino-runtime:${RUST_VERSION}-${COMMIT}" + ZAINOD_TAG="zainod:${RUST_VERSION}-${COMMIT}" +} + +# Return the engine-specific tail file for the final build stage. +tail_file_for_engine() { + if [ "$ENGINE" = "podman" ]; then + echo "Containerfile.tail" + else + echo "Dockerfile.tail" + fi +} + +# Return the command prefix needed to invoke zainod in the container. +# Podman images have ENTRYPOINT ["zainod"], so no prefix is needed. +# Docker images have no entrypoint, so the binary name must be given. +cmd_prefix_for_engine() { + if [ "$ENGINE" = "podman" ]; then + echo "" + else + echo "zainod" + fi +} + +# Return extra flags for "$ENGINE run" that differ between engines. +# Podman: --userns=keep-id maps the host UID into the container. +# Docker: --user flag to run as the host user. +run_flags_for_engine() { + if [ "$ENGINE" = "podman" ]; then + echo "--userns=keep-id" + else + echo "--user $(id -u):$(id -g)" + fi +} +