#!/usr/bin/env bash # ──────────────────────────────────────────────────────────────────── # DECT relay agent — dev-machine bundler. # # Builds the Docker image for linux/amd64, saves it as a gzipped # tarball, and zips it up with the compose file + .env template + # install script. Output is a self-contained ZIP the DC operator # can transfer over any file-copy channel (email, S3, USB, git-lfs) # and install with a single `./install.sh` invocation. # # Requirements on the dev machine: # - Docker Desktop / Docker Engine # - Internet access to pull node:20-alpine + npm registry # - `zip` (macOS + most Linux distros already have it; if not, # `apt install zip` / `brew install zip`) # # Requirements on the DC host: # - Docker + Docker Compose v2 (v1 also works) # - Ability to `docker load` (i.e. member of the docker group or # root) # - Outbound HTTPS to the bot + 10.0.0.0/8 on TCP 443 # - That's it. No npm, no python, no Alpine mirrors. # # Usage (from repo root OR from this folder — the script figures it out): # ./dect-relay-agent/bundle.sh # uses version from package.json # ./dect-relay-agent/bundle.sh --tag 0.2.0 # override version # ./dect-relay-agent/bundle.sh --platform linux/arm64 # ──────────────────────────────────────────────────────────────────── set -euo pipefail # ─── Argument parsing ───────────────────────────────────────────── TAG_OVERRIDE="" PLATFORM="linux/amd64" # standard x86_64 Linux server. Override if DC is arm. while [[ $# -gt 0 ]]; do case "$1" in --tag) TAG_OVERRIDE="$2"; shift 2 ;; --platform) PLATFORM="$2"; shift 2 ;; -h|--help) grep -E '^# ' "$0" | sed 's/^# \?//' exit 0 ;; *) echo "Unknown flag: $1" >&2; exit 2 ;; esac done # ─── Locate paths (works from repo root or agent dir) ───────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" AGENT_DIR="$SCRIPT_DIR" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Sanity: the Dockerfile expects to build from the repo root. [[ -d "$REPO_ROOT/integrations/cisco-dect" ]] || { echo "ERROR: $REPO_ROOT does not look like the collabSupport repo root (no integrations/cisco-dect/)" >&2 exit 1 } # ─── Determine version tag ──────────────────────────────────────── if [[ -n "$TAG_OVERRIDE" ]]; then VERSION="$TAG_OVERRIDE" else # Pull version from agent's package.json without needing jq. The # regex is deliberately tolerant of trailing commas / whitespace. VERSION="$(sed -nE 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$AGENT_DIR/package.json" | head -1)" [[ -n "$VERSION" ]] || { echo "ERROR: could not read version from package.json" >&2; exit 1; } fi # Try to record the git commit into the image labels — useful in # production for "which build am I running?". Missing git is fine. GIT_COMMIT="unknown" if command -v git >/dev/null 2>&1 && git -C "$REPO_ROOT" rev-parse --short HEAD >/dev/null 2>&1; then GIT_COMMIT="$(git -C "$REPO_ROOT" rev-parse --short HEAD)" # Mark as dirty if the working tree has uncommitted changes — # catches "I built from local edits" surprises in production. if ! git -C "$REPO_ROOT" diff --quiet 2>/dev/null || \ ! git -C "$REPO_ROOT" diff --cached --quiet 2>/dev/null; then GIT_COMMIT="${GIT_COMMIT}-dirty" fi fi BUILD_DATE="$(date -u +%FT%TZ)" IMAGE_TAG="collabsupport/dect-relay-agent:${VERSION}" BUNDLE_STAMP="$(date +%Y%m%d-%H%M%S)" BUNDLE_DIR="dect-relay-agent-bundle-${BUNDLE_STAMP}" BUNDLE_ZIP="${BUNDLE_DIR}.zip" echo "════════════════════════════════════════════════════════════════" echo " Building DECT relay agent bundle" echo "────────────────────────────────────────────────────────────────" echo " Version: ${VERSION}" echo " Image tag: ${IMAGE_TAG}" echo " Platform: ${PLATFORM}" echo " Git commit: ${GIT_COMMIT}" echo " Build date: ${BUILD_DATE}" echo " Bundle dir: ${BUNDLE_DIR}/" echo " Bundle ZIP: ${BUNDLE_ZIP}" echo "════════════════════════════════════════════════════════════════" # ─── Docker build ───────────────────────────────────────────────── # --platform pins the arch so building on Apple Silicon still # produces an x86_64 image the DC can run. Docker uses QEMU to # emulate cross-arch — slower than native but Just Works. echo echo "[1/4] Building image..." docker build \ --platform="${PLATFORM}" \ --file "${AGENT_DIR}/Dockerfile" \ --tag "${IMAGE_TAG}" \ --build-arg "AGENT_VERSION=${VERSION}" \ --build-arg "BUILD_DATE=${BUILD_DATE}" \ --build-arg "GIT_COMMIT=${GIT_COMMIT}" \ "${REPO_ROOT}" # ─── Stage the bundle ───────────────────────────────────────────── # Work in the parent of the agent dir so the resulting ZIP + dir # both land somewhere obvious (the repo root by convention). echo echo "[2/4] Staging bundle in ${REPO_ROOT}/${BUNDLE_DIR}/" rm -rf "${REPO_ROOT:?}/${BUNDLE_DIR}" mkdir -p "${REPO_ROOT}/${BUNDLE_DIR}" # docker save streams a tarball to stdout; pipe through gzip to # shrink it substantially (typically ~40% smaller for Node images). echo echo "[3/4] Saving image to ${BUNDLE_DIR}/image.tar.gz (this can take a minute)" docker save "${IMAGE_TAG}" | gzip -9 > "${REPO_ROOT}/${BUNDLE_DIR}/image.tar.gz" # Copy the operator-facing files. We do NOT copy Dockerfile / bundle.sh # — those are dev-machine concerns. cp "${AGENT_DIR}/docker-compose.yml" "${REPO_ROOT}/${BUNDLE_DIR}/" cp "${AGENT_DIR}/.env.example" "${REPO_ROOT}/${BUNDLE_DIR}/" cp "${AGENT_DIR}/install.sh" "${REPO_ROOT}/${BUNDLE_DIR}/" cp "${AGENT_DIR}/README.md" "${REPO_ROOT}/${BUNDLE_DIR}/AGENT-README.md" # Write a bundle-specific README that's short and tells the operator # what to do in this exact folder. Keeps AGENT-README.md as the deep # reference without cluttering the top-of-bundle experience. cat > "${REPO_ROOT}/${BUNDLE_DIR}/README.txt" <