collabSupport/dect-relay-agent/bundle.sh
jmcqueen f7953b8eb5 Add MPP desk phone diagnostics follow-up to /phonestatus via relay.
Wire CP-78xx probe discovery, relay phone-probe commands, and a chat follow-up message so store desk phones get registration, switch, and provisioning detail alongside DECT and WAN diagnostics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:01:01 -04:00

208 lines
9.7 KiB
Bash
Executable file

#!/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
}
[[ -d "$REPO_ROOT/integrations/cisco-mpp-phone" ]] || {
echo "ERROR: $REPO_ROOT is missing integrations/cisco-mpp-phone/ (required for MPP phone-probe)" >&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" <<EOF
DECT Relay Agent — deployment bundle
====================================
Version: ${VERSION}
Image tag: ${IMAGE_TAG}
Built: ${BUILD_DATE}
Git commit: ${GIT_COMMIT}
Platform: ${PLATFORM}
To install on this data-center host:
1. cp .env.example .env
2. Edit .env — set:
- DECT_RELAY_BOT_URL (wss:// URL to the bot)
- DECT_RELAY_AGENT_TOKEN (shared bearer, same as bot's env)
- DECT_ADMIN_PASSWORD (DECT serviceability password)
3. ./install.sh
4. Watch it come up: docker compose logs -f dect-relay-agent
Files in this bundle:
image.tar.gz Prebuilt Docker image (gzipped, ~40MB)
docker-compose.yml Compose file — read-only rootfs, host network,
log rotation. Loaded by install.sh.
.env.example Config template.
install.sh Runs 'docker load' then 'docker compose up -d'.
Safe to re-run for upgrades.
AGENT-README.md Full agent docs — wire protocol, safety model,
run instructions.
README.txt This file.
No internet access required after the image is loaded. The container
runs with a read-only root filesystem, drops all Linux capabilities,
and uses the 'node' non-root user.
Upgrading:
Unzip the new bundle in a new folder, or overwrite this one, and
re-run ./install.sh. install.sh pins the new image tag into .env
automatically.
EOF
# ─── Zip it up ────────────────────────────────────────────────────
echo
echo "[4/4] Zipping bundle → ${BUNDLE_ZIP}"
(
cd "${REPO_ROOT}"
# -r recursive, -X strip Mac resource forks so we don't ship
# __MACOSX/ folders that confuse Linux operators.
zip -rqX "${BUNDLE_ZIP}" "${BUNDLE_DIR}"
)
# Cleanup: keep the ZIP, remove the staging dir. Operator only wants
# the ZIP to transfer.
rm -rf "${REPO_ROOT:?}/${BUNDLE_DIR}"
SIZE="$(du -h "${REPO_ROOT}/${BUNDLE_ZIP}" | cut -f1)"
echo
echo "════════════════════════════════════════════════════════════════"
echo " Bundle ready: ${REPO_ROOT}/${BUNDLE_ZIP} (${SIZE})"
echo "────────────────────────────────────────────────────────────────"
echo " Transfer to the DC and:"
echo " unzip ${BUNDLE_ZIP}"
echo " cd ${BUNDLE_DIR}"
echo " cp .env.example .env && \$EDITOR .env"
echo " ./install.sh"
echo "════════════════════════════════════════════════════════════════"