#!/usr/bin/env bash # Build HTKnow on a server without installing Node.js or Rust on the host. # # Examples: # ./build-docker.sh # ./build-docker.sh --tag gwknow:v0.0.2 # ./build-docker.sh --skip-frontend --tag gwknow:v0.0.3 # IMAGE_TAG=v0.0.3 ./build-docker.sh set -Eeuo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" NODE_IMAGE="${NODE_IMAGE:-docker.1ms.run/node:22}" RUST_BUILDER_IMAGE="${RUST_BUILDER_IMAGE:-htknow-rust-builder:bookworm}" SKIP_FRONTEND=false REBUILD_BUILDER=false IMAGE_TAG="${IMAGE_TAG:-}" usage() { cat <<'EOF' Usage: ./build-docker.sh [options] Options: --tag IMAGE Target image tag, for example gwknow:v0.0.2. --skip-frontend Do not rebuild frontend/dist (safe for Rust-only changes). --rebuild-builder Rebuild the cached Rust + protoc builder image. -h, --help Show this help. Environment overrides: IMAGE_TAG, NODE_IMAGE, RUST_BUILDER_IMAGE If --tag and IMAGE_TAG are omitted, a new gwknow:YYYYmmdd-HHMMSS tag is used. EOF } while [[ $# -gt 0 ]]; do case "$1" in --tag) [[ $# -ge 2 ]] || { echo "--tag requires an image tag" >&2; exit 2; } IMAGE_TAG="$2" shift 2 ;; --skip-frontend|--backend-only) SKIP_FRONTEND=true shift ;; --rebuild-builder) REBUILD_BUILDER=true shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" >&2 usage >&2 exit 2 ;; esac done command -v docker >/dev/null || { echo "docker is not installed or is not in PATH" >&2; exit 1; } docker info >/dev/null 2>&1 || { echo "Docker daemon is unavailable" >&2; exit 1; } if [[ -z "$IMAGE_TAG" ]]; then IMAGE_TAG="gwknow:$(date +%Y%m%d-%H%M%S)" elif [[ "$IMAGE_TAG" != *:* ]]; then IMAGE_TAG="gwknow:${IMAGE_TAG}" fi HOST_UID="$(id -u)" HOST_GID="$(id -g)" mkdir -p .cargo-cache .npm-cache if [[ "$REBUILD_BUILDER" == true ]] || ! docker image inspect "$RUST_BUILDER_IMAGE" >/dev/null 2>&1; then echo "==> Building Rust builder image: $RUST_BUILDER_IMAGE" docker build -t "$RUST_BUILDER_IMAGE" -f Dockerfile.rust-builder . fi if [[ "$SKIP_FRONTEND" == false ]]; then echo "==> Building frontend with $NODE_IMAGE" docker run --rm \ --user "${HOST_UID}:${HOST_GID}" \ -e HOME=/tmp \ -e npm_config_cache=/npm-cache \ -v "$ROOT_DIR":/app \ -v "$ROOT_DIR/.npm-cache":/npm-cache \ -w /app/frontend \ "$NODE_IMAGE" \ sh -c 'npm ci && npm run build' else echo "==> Skipping frontend build (--skip-frontend)" fi echo "==> Building Rust release binary with $RUST_BUILDER_IMAGE" docker run --rm \ --user "${HOST_UID}:${HOST_GID}" \ -e HOME=/tmp \ -e CARGO_HOME=/cargo-cache \ -v "$ROOT_DIR":/app \ -v "$ROOT_DIR/.cargo-cache":/cargo-cache \ -w /app \ "$RUST_BUILDER_IMAGE" \ bash -c 'cargo build --locked --release' echo "==> Building runtime image: $IMAGE_TAG" docker build -f Dockerfile -t "$IMAGE_TAG" . echo echo "Build complete: $IMAGE_TAG" echo "To deploy, update the compose image tag and recreate only the intended service."