#!/usr/bin/env bash # # Package the StoreHealthAnalyzer remote agent into a self-contained ZIP # for offline / manual transfer to a remote Docker host. # # What this script does: # 1. Reads the version from docker/remote-agent/package.json. # 2. Builds `sha-remote-agent:` from the local source tree. # 3. `docker save`s the image, gzip-compressed, into a temp staging dir. # 4. Copies deploy/docker-compose.yml, deploy/install.sh, deploy/README.md, # and .env.example into the staging dir. Rewrites the compose file's # __VERSION__ placeholder to match the built image tag. # 5. Writes VERSION and SHA256SUMS files for identification / integrity. # 6. Zips the whole staging dir into docker/remote-agent/dist/. # # Usage: # ./docker/remote-agent/package.sh # tag=package.json, platform=linux/amd64 # ./docker/remote-agent/package.sh --tag 1.0.1 # override tag # ./docker/remote-agent/package.sh --platform linux/arm64 # ARM Linux target # ./docker/remote-agent/package.sh --platform linux/amd64 # explicit default (Linux RH/Rocky/CentOS/Ubuntu on Intel) # # The image is ALWAYS built for the target platform via `docker buildx # build --platform ...` so the tarball you ship matches the remote host. # Default is linux/amd64 because that's the overwhelmingly common Linux # server architecture; override with --platform if your remote host is # something else (e.g. linux/arm64 for a Raspberry Pi or ARM-based server). # # Requires: docker (with buildx), zip, node (for reading package.json), # sha256sum OR shasum (macOS ships shasum by default). set -euo pipefail # --- Locations -------------------------------------------------------------- # Absolute path to this script's directory; then one level up is the repo root. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" DIST_DIR="$SCRIPT_DIR/dist" DEPLOY_DIR="$SCRIPT_DIR/deploy" DOCKERFILE="$SCRIPT_DIR/Dockerfile" # --- Colors ----------------------------------------------------------------- GRN=$'\033[0;32m' YLW=$'\033[1;33m' RED=$'\033[0;31m' RST=$'\033[0m' log() { printf '%s[package]%s %s\n' "$GRN" "$RST" "$*"; } warn() { printf '%s[package]%s %s\n' "$YLW" "$RST" "$*"; } die() { printf '%s[package]%s %s\n' "$RED" "$RST" "$*" >&2; exit 1; } # --- Argument parsing ------------------------------------------------------- VERSION="" # Default target platform. Overwhelming majority of Linux server hosts # (RHEL, Rocky, CentOS, Ubuntu, Debian) run on x86_64. Override with # --platform for ARM Linux (linux/arm64) or anything else. PLATFORM="linux/amd64" while [[ $# -gt 0 ]]; do case "$1" in --tag) shift VERSION="${1:-}" shift || true ;; --platform) shift PLATFORM="${1:-}" shift || true ;; -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac done [[ -n "$PLATFORM" ]] || die "--platform requires a value (e.g. linux/amd64, linux/arm64)" # --- Preflight -------------------------------------------------------------- command -v docker >/dev/null 2>&1 || die "docker not found on PATH." command -v zip >/dev/null 2>&1 || die "zip not found on PATH." command -v node >/dev/null 2>&1 || die "node not found on PATH." # buildx is required so we can cross-build for a specific target platform # on any host (e.g. build linux/amd64 from an Apple Silicon Mac). docker buildx version >/dev/null 2>&1 \ || die "docker buildx not available. Install Docker Desktop or the buildx plugin." if [[ -z "$VERSION" ]]; then VERSION="$(node -p "require('$SCRIPT_DIR/package.json').version")" fi [[ -n "$VERSION" ]] || die "Could not determine version." log "Packaging sha-remote-agent version: ${VERSION}" log "Target platform: ${PLATFORM}" IMAGE_TAG="sha-remote-agent:${VERSION}" BUNDLE_NAME="sha-remote-agent-${VERSION}" STAGING_DIR="$(mktemp -d)" STAGING_ROOT="$STAGING_DIR/$BUNDLE_NAME" mkdir -p "$STAGING_ROOT" # Guarantee cleanup even on error. cleanup() { rm -rf "$STAGING_DIR"; } trap cleanup EXIT # --- 1. Build the image ----------------------------------------------------- log "Building Docker image ${IMAGE_TAG} for ${PLATFORM} (context = ${REPO_ROOT})..." # buildx with --load emits the image straight into the local Docker daemon # so `docker save` in the next step picks it up. --load only supports one # platform at a time, which matches our "one ZIP per target" workflow. docker buildx build \ --platform "$PLATFORM" \ --load \ -f "$DOCKERFILE" \ -t "$IMAGE_TAG" \ -t "sha-remote-agent:latest" \ "$REPO_ROOT" # --- 2. Save the image to a gzipped tarball -------------------------------- IMAGE_TARBALL="${BUNDLE_NAME}.tar.gz" log "Saving image to ${IMAGE_TARBALL}..." docker save "$IMAGE_TAG" | gzip > "$STAGING_ROOT/$IMAGE_TARBALL" TAR_SIZE_MB="$(du -m "$STAGING_ROOT/$IMAGE_TARBALL" | cut -f1)" log "Image tarball size: ${TAR_SIZE_MB} MB" # --- 3. Copy deploy assets -------------------------------------------------- log "Copying deploy assets into bundle..." cp "$SCRIPT_DIR/.env.example" "$STAGING_ROOT/.env.example" cp "$DEPLOY_DIR/install.sh" "$STAGING_ROOT/install.sh" cp "$DEPLOY_DIR/README.md" "$STAGING_ROOT/README.md" # Template the version into the runtime compose file so it references the # specific image tag we just built. sed "s|__VERSION__|${VERSION}|g" \ "$DEPLOY_DIR/docker-compose.yml" > "$STAGING_ROOT/docker-compose.yml" chmod +x "$STAGING_ROOT/install.sh" # --- 4. VERSION + SHA256SUMS ----------------------------------------------- printf '%s\n' "$VERSION" > "$STAGING_ROOT/VERSION" log "Computing SHA-256 checksum for the image tarball..." pushd "$STAGING_ROOT" >/dev/null if command -v sha256sum >/dev/null 2>&1; then sha256sum "$IMAGE_TARBALL" > SHA256SUMS elif command -v shasum >/dev/null 2>&1; then # macOS: shasum -a 256 emits the same " " format # `sha256sum -c` understands. shasum -a 256 "$IMAGE_TARBALL" > SHA256SUMS else warn "No sha256sum/shasum available — skipping checksum file." fi popd >/dev/null # --- 5. Zip ----------------------------------------------------------------- mkdir -p "$DIST_DIR" ZIP_PATH="$DIST_DIR/${BUNDLE_NAME}.zip" rm -f "$ZIP_PATH" log "Creating ZIP: ${ZIP_PATH}" # Zip from inside the temp dir so the archive contains the top-level folder # with the bundle name (matching what install.sh expects when unzipped). (cd "$STAGING_DIR" && zip -qr "$ZIP_PATH" "$BUNDLE_NAME") ZIP_SIZE_MB="$(du -m "$ZIP_PATH" | cut -f1)" # --- 6. Done --------------------------------------------------------------- log "" log "==========================================================" log " Bundle ready:" log " $ZIP_PATH" log " (${ZIP_SIZE_MB} MB, built for ${PLATFORM})" log "==========================================================" log "" log "Transfer to the remote host, then:" log " unzip ${BUNDLE_NAME}.zip" log " cd ${BUNDLE_NAME}" log " ./install.sh"