#!/usr/bin/env bash set -Eeuo pipefail # Apply an incremental image update on the target runtime server. # Usage: # bash apply-update.sh # bash apply-update.sh emp-admin emp-monitor # DEPLOY_ENV=emp-uat ENV_FILE=.env COMPOSE_FILE=docker-compose.yml bash apply-update.sh # COMPOSE_FILES="/path/docker-compose.yml:/path/docker-compose.logs.yml" bash apply-update.sh SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" PROJECT_NAME="${PROJECT_NAME:-$DEPLOY_ENV}" DEPLOY_HOME="${DEPLOY_HOME:-/home/admin-x99/$DEPLOY_ENV}" ENV_FILE="${ENV_FILE:-.env}" COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" COMPOSE_FILES="${COMPOSE_FILES:-}" IMAGE_TAR="${IMAGE_TAR:-images.tar}" SERVICE_FILE="${SERVICE_FILE:-services.txt}" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } die() { echo "ERROR: $*" >&2 exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" } normalize_service() { case "$1" in gateway|emp-gateway) echo "emp-gateway" ;; auth|emp-auth) echo "emp-auth" ;; monitor|emp-monitor) echo "emp-monitor" ;; data|emp-data) echo "emp-data" ;; pdf|emp-pdf) echo "emp-pdf" ;; ws|emp-ws) echo "emp-ws" ;; admin|emp-admin) echo "emp-admin" ;; *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;; esac } resolve_compose_cmd() { if docker compose version >/dev/null 2>&1; then echo "docker compose" elif command -v docker-compose >/dev/null 2>&1; then echo "docker-compose" else die "Missing docker compose" fi } resolve_runtime_file() { local requested="$1" local default_name="$2" if [[ -f "$requested" ]]; then echo "$requested" return fi if [[ "$requested" != "$default_name" ]]; then echo "$requested" return fi local candidates=( "../runtime/$default_name" "$DEPLOY_HOME/runtime/$default_name" "$DEPLOY_HOME/$default_name" "/opt/$DEPLOY_ENV/$default_name" ) local candidate for candidate in "${candidates[@]}"; do if [[ -f "$candidate" ]]; then echo "$candidate" return fi done echo "$requested" } resolve_compose_files() { local raw_files=() local file resolved if [[ -n "$COMPOSE_FILES" ]]; then IFS=':' read -r -a raw_files <<< "$COMPOSE_FILES" else raw_files=("$COMPOSE_FILE") fi COMPOSE_ARGS=() RESOLVED_COMPOSE_FILES=() for file in "${raw_files[@]}"; do [[ -n "$file" ]] || continue resolved="$(resolve_runtime_file "$file" "docker-compose.yml")" [[ -f "$resolved" ]] || die "Missing compose file: $resolved" RESOLVED_COMPOSE_FILES+=("$resolved") COMPOSE_ARGS+=("-f" "$resolved") done [[ "${#COMPOSE_ARGS[@]}" -gt 0 ]] || die "No compose files resolved." } read_services() { local raw_services=() if [[ "$#" -gt 0 ]]; then raw_services=("$@") elif [[ -f "$SERVICE_FILE" ]]; then mapfile -t raw_services < "$SERVICE_FILE" else die "No services specified. Pass services as args or provide $SERVICE_FILE." fi local service for service in "${raw_services[@]}"; do [[ -n "$service" ]] || continue normalize_service "$service" done } need_cmd docker ENV_FILE="$(resolve_runtime_file "$ENV_FILE" ".env")" resolve_compose_files [[ -f "$ENV_FILE" ]] || die "Missing env file: $ENV_FILE" [[ -f "$IMAGE_TAR" ]] || die "Missing image tar: $IMAGE_TAR" mapfile -t UPDATE_SERVICES < <(read_services "$@") [[ "${#UPDATE_SERVICES[@]}" -gt 0 ]] || die "No services to update." COMPOSE_CMD="$(resolve_compose_cmd)" log "Compose files: ${RESOLVED_COMPOSE_FILES[*]}" log "Load images: $IMAGE_TAR" docker load -i "$IMAGE_TAR" log "Recreate services: ${UPDATE_SERVICES[*]}" # shellcheck disable=SC2086 $COMPOSE_CMD \ --env-file "$ENV_FILE" \ "${COMPOSE_ARGS[@]}" \ -p "$PROJECT_NAME" \ up -d --no-deps --force-recreate "${UPDATE_SERVICES[@]}" log "Current service status" # shellcheck disable=SC2086 $COMPOSE_CMD \ --env-file "$ENV_FILE" \ "${COMPOSE_ARGS[@]}" \ -p "$PROJECT_NAME" \ ps "${UPDATE_SERVICES[@]}"