#!/usr/bin/env bash # # StoreHealthAnalyzer Remote Agent — install / (re)start on a remote host. # # Run this after extracting the deploy ZIP: # unzip sha-remote-agent-.zip # cd sha-remote-agent- # ./install.sh # # On first run: verifies the image tarball, loads it into Docker, and drops # a starter .env so you can fill in WS_URL / WS_TOKEN. Re-runs are safe — # the script is idempotent. set -euo pipefail # Move to the script's own directory so relative paths work regardless of # where the user invoked it from. cd "$(dirname "$0")" RED=$'\033[0;31m' GRN=$'\033[0;32m' YLW=$'\033[1;33m' RST=$'\033[0m' log() { printf '%s[install]%s %s\n' "$GRN" "$RST" "$*"; } warn() { printf '%s[install]%s %s\n' "$YLW" "$RST" "$*"; } die() { printf '%s[install]%s %s\n' "$RED" "$RST" "$*" >&2; exit 1; } # --- 1. Preflight ---------------------------------------------------------- command -v docker >/dev/null 2>&1 || die "Docker not found on PATH." docker info >/dev/null 2>&1 \ || die "Cannot talk to the Docker daemon. Is it running / do you have permission?" # Detect either `docker compose` (v2 plugin) or the legacy `docker-compose`. if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose) elif command -v docker-compose >/dev/null 2>&1; then COMPOSE=(docker-compose) else die "Neither 'docker compose' nor 'docker-compose' is available. Install Docker Compose." fi [[ -f VERSION ]] || die "VERSION file missing from bundle — is this a valid deploy ZIP?" VERSION="$(cat VERSION)" IMAGE_TARBALL="sha-remote-agent-${VERSION}.tar.gz" [[ -f "$IMAGE_TARBALL" ]] || die "Image tarball not found: $IMAGE_TARBALL" # --- 2. Verify checksum (optional; skip gracefully if no shasum tool) ----- if [[ -f SHA256SUMS ]]; then if command -v sha256sum >/dev/null 2>&1; then log "Verifying SHA256 checksum..." sha256sum -c SHA256SUMS >/dev/null || die "Checksum verification FAILED." elif command -v shasum >/dev/null 2>&1; then log "Verifying SHA256 checksum (macOS shasum)..." shasum -a 256 -c SHA256SUMS >/dev/null || die "Checksum verification FAILED." else warn "No sha256sum/shasum tool found — skipping integrity check." fi log "Checksum OK." else warn "No SHA256SUMS file in bundle — skipping integrity check." fi # --- 3. Load the image ----------------------------------------------------- # ALWAYS docker load, unconditionally. `docker load` reassigns the tag to # whatever's in the tarball and is a fast no-op when the layers are already # present, so this is safe to re-run. We intentionally do NOT "skip if the # tag already exists" — a stale image left over from a previous wrong-arch # attempt has this exact tag, and skipping the load would hide the correct # image in the tarball we're actually holding. IMAGE_TAG="sha-remote-agent:${VERSION}" log "Loading Docker image from ${IMAGE_TARBALL}..." docker load -i "$IMAGE_TARBALL" # --- 3a. Platform sanity check -------------------------------------------- # Inspect the image we just (re)loaded — this is the ground truth for what # was actually shipped in this ZIP, independent of anything that was on the # host before. If the tarball was built for a different CPU architecture # than this host, Docker will let it "run" but tini (and node) fail with # cryptic errors like "exec format error". Catch that up front with a # clear message. IMAGE_ARCH="$(docker image inspect --format '{{.Architecture}}' "$IMAGE_TAG" 2>/dev/null || true)" IMAGE_ID_SHORT="$(docker image inspect --format '{{.Id}}' "$IMAGE_TAG" 2>/dev/null | sed 's|^sha256:||' | cut -c1-12)" HOST_ARCH_RAW="$(uname -m)" case "$HOST_ARCH_RAW" in x86_64|amd64) HOST_ARCH="amd64" ;; aarch64|arm64) HOST_ARCH="arm64" ;; armv7l) HOST_ARCH="arm" ;; *) HOST_ARCH="$HOST_ARCH_RAW" ;; esac log "Loaded ${IMAGE_TAG} (id: ${IMAGE_ID_SHORT:-unknown}, arch: ${IMAGE_ARCH:-unknown}); host arch: ${HOST_ARCH}." if [[ -z "$IMAGE_ARCH" ]]; then die "Could not read image architecture from Docker — is the image really loaded?" fi if [[ "$IMAGE_ARCH" != "$HOST_ARCH" ]]; then warn "Image architecture (${IMAGE_ARCH}) does not match this host (${HOST_ARCH})." warn "The container would fail to start with 'exec /sbin/tini: exec format error'." warn "" warn "This means the ZIP was built for the wrong CPU. On the build host:" warn " 1. git pull # make sure you have the fixed package.sh" warn " 2. ./docker/remote-agent/package.sh --platform linux/${HOST_ARCH}" warn "The updated package.sh verifies the architecture during build and" warn "refuses to produce a ZIP that would fail this check." die "Aborting install. Ship a linux/${HOST_ARCH} bundle and re-run this script." fi # --- 4. Bootstrap .env ----------------------------------------------------- if [[ ! -f .env ]]; then if [[ -f .env.example ]]; then cp .env.example .env warn ".env did not exist — copied .env.example into place." warn "EDIT .env now to set WS_URL and WS_TOKEN, then re-run this script." exit 0 else die ".env is missing and no .env.example is bundled. Cannot proceed." fi fi # --- 5. Start the container ----------------------------------------------- log "Starting sha-remote-agent (version ${VERSION})..." "${COMPOSE[@]}" up -d log "Done. Tail logs with:" log " ${COMPOSE[*]} logs -f" log "Stop with:" log " ${COMPOSE[*]} down"