Files
managed-portal/deploy/install-managed-portal-ota.sh

288 lines
7.9 KiB
Bash

#!/usr/bin/env sh
set -eu
RELEASE_VERSION="${RELEASE_VERSION:-20260518-7b32b21-11}"
BASE_URL="${BASE_URL:-http://10.8.0.1/ai_deploy}"
BUNDLE_NAME="${BUNDLE_NAME:-managed-portal-${RELEASE_VERSION}.zip}"
WEIGHTS_ARCHIVE_NAME="${WEIGHTS_ARCHIVE_NAME:-people-flow-weights-${RELEASE_VERSION}.tar.gz}"
INSTALL_ROOT="${INSTALL_ROOT:-/opt/managed-portal-releases}"
TARGET_DIR="${TARGET_DIR:-${INSTALL_ROOT}/managed-portal-${RELEASE_VERSION}}"
SHARED_ROOT="${SHARED_ROOT:-${INSTALL_ROOT}/shared}"
DEFAULT_PEOPLE_FLOW_WEIGHTS_DIR="${SHARED_ROOT}/people_flow_project/weights"
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing required command: $1" >&2
exit 1
fi
}
ensure_ubuntu_package() {
package_name="$1"
command_name="$2"
if command -v "$command_name" >/dev/null 2>&1; then
return 0
fi
if [ ! -r /etc/os-release ]; then
echo "missing required command: $command_name; unable to detect OS for automatic installation" >&2
exit 1
fi
os_id="$(. /etc/os-release && printf '%s' "${ID:-}")"
os_like="$(. /etc/os-release && printf '%s' "${ID_LIKE:-}")"
case "$os_id:$os_like" in
ubuntu:*|*:ubuntu*|debian:*|*:debian*)
;;
*)
echo "missing required command: $command_name; automatic installation is only supported on Ubuntu/Debian hosts" >&2
exit 1
;;
esac
if [ "$(id -u)" -ne 0 ]; then
echo "missing required command: $command_name; rerun installer as root on Ubuntu to auto-install $package_name" >&2
exit 1
fi
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y "$package_name"
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "failed to install required command: $command_name" >&2
exit 1
fi
}
pull_or_use_local() {
image="$1"
if docker pull "$image"; then
return 0
fi
echo "docker pull failed for $image, checking local image cache" >&2
if docker image inspect "$image" >/dev/null 2>&1; then
echo "using existing local image $image" >&2
return 0
fi
echo "image unavailable locally after pull failure: $image" >&2
exit 1
}
run_compose() {
if command -v docker-compose >/dev/null 2>&1; then
docker-compose "$@"
return 0
fi
docker compose "$@"
}
dir_has_files() {
directory="$1"
[ -d "$directory" ] && [ -n "$(find "$directory" -mindepth 1 -print -quit 2>/dev/null)" ]
}
dir_has_payload_files() {
directory="$1"
[ -d "$directory" ] && [ -n "$(find "$directory" -type f ! -name '.gitkeep' -print -quit 2>/dev/null)" ]
}
copy_dir_contents() {
source_dir="$1"
target_dir="$2"
mkdir -p "$target_dir"
cp -R "$source_dir"/. "$target_dir"/
}
download_bundle() {
tmp_dir="$1"
bundle_zip="$tmp_dir/$BUNDLE_NAME"
bundle_url="${BASE_URL%/}/$BUNDLE_NAME"
echo "downloading $bundle_url" >&2
curl -fL "$bundle_url" -o "$bundle_zip"
echo "$bundle_zip"
}
download_weights_archive() {
tmp_dir="$1"
weights_archive="$tmp_dir/$WEIGHTS_ARCHIVE_NAME"
weights_url="${BASE_URL%/}/$WEIGHTS_ARCHIVE_NAME"
echo "downloading $weights_url" >&2
curl -fL "$weights_url" -o "$weights_archive"
echo "$weights_archive"
}
build_overlay_image() {
overlay_name="$1"
base_image="$2"
overlay_root="$3"
overlay_image="$4"
overlay_context="$(dirname "$overlay_root")"
if [ ! -d "$overlay_root" ]; then
printf '%s\n' "$base_image"
return 0
fi
if [ -z "$(find "$overlay_root" -mindepth 1 -print -quit)" ]; then
printf '%s\n' "$base_image"
return 0
fi
echo "building runtime overlay for $overlay_name" >&2
docker build \
-f "$TARGET_DIR/deploy/Dockerfile.runtime-overlay" \
--build-arg "BASE_IMAGE=$base_image" \
-t "$overlay_image" \
"$overlay_context" >/dev/null
printf '%s\n' "$overlay_image"
}
clear_stale_runtime_state() {
people_flow_status="$TARGET_DIR/managed/people_flow_project/outputs/rtsp_stream/worker_status.json"
if [ -f "$people_flow_status" ]; then
rm -f "$people_flow_status"
fi
}
ensure_runtime_directories() {
mkdir -p \
"$TARGET_DIR/managed/store_dwell_alert/config" \
"$TARGET_DIR/managed/store_dwell_alert/data" \
"$TARGET_DIR/managed/people_flow_project/config" \
"$TARGET_DIR/managed/people_flow_project/outputs/rtsp_stream"
}
find_existing_people_flow_weights() {
if dir_has_files "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR"; then
printf '%s\n' "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR"
return 0
fi
for candidate in "$INSTALL_ROOT"/managed-portal-*/managed/people_flow_project/weights; do
if [ "$candidate" = "$TARGET_DIR/managed/people_flow_project/weights" ]; then
continue
fi
if dir_has_files "$candidate"; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
extract_people_flow_weights_archive() {
tmp_dir="$1"
bundle_weights_dir="$TARGET_DIR/managed/people_flow_project/weights"
weights_archive="$(download_weights_archive "$tmp_dir")"
mkdir -p "$TARGET_DIR/managed"
tar -xzf "$weights_archive" -C "$TARGET_DIR/managed"
if ! dir_has_payload_files "$bundle_weights_dir"; then
echo "downloaded weights archive did not populate $bundle_weights_dir" >&2
exit 1
fi
printf '%s\n' "$bundle_weights_dir"
}
prepare_people_flow_weights() {
tmp_dir="$1"
bundle_weights_dir="$TARGET_DIR/managed/people_flow_project/weights"
source_weights_dir=""
if dir_has_payload_files "$bundle_weights_dir"; then
source_weights_dir="$bundle_weights_dir"
echo "seeding shared people-flow weights from bundle" >&2
elif source_weights_dir="$(find_existing_people_flow_weights 2>/dev/null)"; then
echo "reusing existing people-flow weights from $source_weights_dir" >&2
elif source_weights_dir="$(extract_people_flow_weights_archive "$tmp_dir" 2>/dev/null)"; then
echo "seeding shared people-flow weights from downloaded archive" >&2
else
echo "people-flow weights not found; seed $MANAGED_PEOPLE_FLOW_WEIGHTS_DIR, publish $WEIGHTS_ARCHIVE_NAME, or include managed/people_flow_project/weights in the release zip" >&2
exit 1
fi
if [ "$source_weights_dir" != "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR" ]; then
rm -rf "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR"
copy_dir_contents "$source_weights_dir" "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR"
fi
rm -rf "$bundle_weights_dir"
mkdir -p "$(dirname "$bundle_weights_dir")"
ln -s "$MANAGED_PEOPLE_FLOW_WEIGHTS_DIR" "$bundle_weights_dir"
}
require_command curl
ensure_ubuntu_package unzip unzip
require_command tar
require_command docker
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT INT TERM
mkdir -p "$INSTALL_ROOT"
bundle_zip="$(download_bundle "$tmp_dir")"
rm -rf "$TARGET_DIR"
unzip -oq "$bundle_zip" -d "$INSTALL_ROOT"
if [ ! -f "$TARGET_DIR/release-manifest.env" ]; then
echo "release-manifest.env not found in $TARGET_DIR" >&2
exit 1
fi
set -a
. "$TARGET_DIR/release-manifest.env"
set +a
MANAGED_PEOPLE_FLOW_WEIGHTS_DIR="${MANAGED_PEOPLE_FLOW_WEIGHTS_DIR:-$DEFAULT_PEOPLE_FLOW_WEIGHTS_DIR}"
ensure_runtime_directories
prepare_people_flow_weights "$tmp_dir"
clear_stale_runtime_state
echo "pulling release images"
pull_or_use_local "$MANAGED_PORTAL_IMAGE"
pull_or_use_local "$MANAGED_PORTAL_WEB_IMAGE"
pull_or_use_local "$PEOPLE_FLOW_PROJECT_IMAGE"
pull_or_use_local "$STORE_DWELL_ALERT_IMAGE"
PEOPLE_FLOW_PROJECT_IMAGE="$(build_overlay_image \
people-flow-project \
"$PEOPLE_FLOW_PROJECT_IMAGE" \
"$TARGET_DIR/runtime-overlays/people-flow-project/rootfs" \
"managed-portal-runtime/people-flow-project:${RELEASE_VERSION}")"
STORE_DWELL_ALERT_IMAGE="$(build_overlay_image \
store-dwell-alert \
"$STORE_DWELL_ALERT_IMAGE" \
"$TARGET_DIR/runtime-overlays/store-dwell-alert/rootfs" \
"managed-portal-runtime/store-dwell-alert:${RELEASE_VERSION}")"
export MANAGED_PORTAL_IMAGE
export MANAGED_PORTAL_WEB_IMAGE
export PEOPLE_FLOW_PROJECT_IMAGE
export STORE_DWELL_ALERT_IMAGE
export MANAGED_PEOPLE_FLOW_WEIGHTS_DIR
cd "$TARGET_DIR/deploy"
run_compose \
--env-file managed-portal.release.env \
-f docker-compose.ota-release.yml \
up -d
echo "release installed under $TARGET_DIR"