#!/bin/bash # Safer bash: we manage errors manually (no -e) but detect pipeline failures set -o pipefail # Quiet mode default; enable verbose with --verbose QUIET=${QUIET:-true} # Parse --verbose early (we'll reparse fully later as well) for arg in "$@"; do if [[ "$arg" == "--verbose" ]]; then QUIET=false; fi done # Per-run logs directory RUN_TS=$(date +%Y%m%d_%H%M%S) LOG_DIR="./build_logs/$RUN_TS" mkdir -p "$LOG_DIR" # Error aggregation ERRORS=() ERROR_LINES=() EXIT_CODE=0 # Helper: run_quiet SERVICE STEP -- CMD ARGS... run_quiet() { local SERVICE="$1"; shift local STEP="$1"; shift # Expect a literal "--" separator before the command if [[ "$1" == "--" ]]; then shift; fi local LOG_FILE="$LOG_DIR/${SERVICE}.${STEP}.log" if [[ "$QUIET" == "true" ]]; then "$@" > /dev/null 2> >(tee -a "$LOG_FILE" >&2) else "$@" > >(tee -a "$LOG_FILE") 2> >(tee -a "$LOG_FILE" >&2) fi local RC=$? echo "$LOG_FILE" > "$LOG_DIR/.last_${SERVICE}_${STEP}.path" return $RC } record_error() { local SERVICE="$1"; local STEP="$2"; local MESSAGE="$3"; local LOG_FILE="$4" ERRORS+=("$SERVICE|$STEP|$LOG_FILE|$MESSAGE") ERROR_LINES+=("$MESSAGE") EXIT_CODE=1 } source ./podman_env_switch.sh dev # Load environment variables source .env # Check if podman is available if ! command -v podman &> /dev/null; then echo "Error: podman not found" exit 1 fi echo "Using container runtime: podman" # Local registry REGISTRY="registry.ask-eve-ai-local.com" # Account prefix voor consistency met Docker Hub ACCOUNT="josakola" # Tag (you might want to use a version or git commit hash) TAG="latest" # Single platform - AMD64 only for simplicity PLATFORM="linux/amd64" # Default action ACTION="both" # Default build options NO_CACHE="" PROGRESS="" DEBUG="" BUILD_BASE="" BASE_ONLY="" # Function to display usage information usage() { echo "Usage: $0 [-b|-p|-bb|--base-only] [--no-cache] [--progress=plain] [--debug] [--verbose] [service1 service2 ...]" echo " -b: Build only" echo " -p: Push only" echo " -bb: Build base image (in addition to services)" echo " --base-only: Build only base image (skip services)" echo " --no-cache: Perform a clean build without using cache" echo " --progress=plain: Show detailed progress of the build" echo " --debug: Enable debug mode for the build" echo " --verbose: Show full output of build/push (default is quiet; logs always saved under ./build_logs/)" echo " If no option is provided, both build and push will be performed." echo " If no services are specified, all eveai_ services and nginx will be processed." echo " All images are built for AMD64 platform (compatible with both x86_64 and Apple Silicon via emulation)." } # Parse command-line options while [[ $# -gt 0 ]]; do case $1 in --verbose) QUIET=false shift ;; -b) ACTION="build" shift ;; -p) ACTION="push" shift ;; -bb) BUILD_BASE="true" shift ;; --base-only) BASE_ONLY="true" shift ;; --no-cache) NO_CACHE="--no-cache" shift ;; --progress=plain) PROGRESS="--progress=plain" shift ;; --debug) DEBUG="--debug" shift ;; -*) echo "Unknown option: $1" usage exit 1 ;; *) break ;; esac done # Function to build base image build_base_image() { echo "🏗️ Building base image... ==============================================================" local BASE_IMAGE_NAME="$REGISTRY/$ACCOUNT/eveai-base:$TAG" echo "Building base image for platform: $PLATFORM" echo "Base image tag: $BASE_IMAGE_NAME" run_quiet base build -- podman build \ --platform "$PLATFORM" \ $NO_CACHE \ $PROGRESS \ $DEBUG \ -t "$ACCOUNT/eveai-base:$TAG" \ -t "$BASE_IMAGE_NAME" \ -f Dockerfile.base \ .. if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/base.build.log" echo "❌ Failed to build base image" record_error base build "❌ Failed to build base image" "$LOG_FILE" return 1 fi if [ "$ACTION" = "push" ] || [ "$ACTION" = "both" ]; then echo "Pushing base image to registry..." run_quiet base push -- podman push "$BASE_IMAGE_NAME" if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/base.push.log" echo "❌ Failed to push base image" record_error base push "❌ Failed to push base image" "$LOG_FILE" return 1 fi fi echo "✅ Base image built successfully" } # Function to check if we should build base image should_build_base() { if [ "$BUILD_BASE" = "true" ] || [ "$BASE_ONLY" = "true" ]; then return 0 # true else return 1 # false fi } # Function to build and/or push a service process_service() { local SERVICE="$1" echo "Processing $SERVICE... ==================================================================" # Extract the build context and dockerfile from the compose file CONTEXT=$(yq e ".services.$SERVICE.build.context" compose_dev.yaml) DOCKERFILE=$(yq e ".services.$SERVICE.build.dockerfile" compose_dev.yaml) # Check if context directory exists if [ ! -d "$CONTEXT" ]; then echo "Error: Build context directory '$CONTEXT' for service '$SERVICE' does not exist." return 1 fi # Check if Dockerfile exists if [ ! -f "$CONTEXT/$DOCKERFILE" ]; then echo "Error: Dockerfile '$DOCKERFILE' for service '$SERVICE' does not exist in context '$CONTEXT'." return 1 fi # Construct image names LOCAL_IMAGE_NAME="$ACCOUNT/$SERVICE:$TAG" REGISTRY_IMAGE_NAME="$REGISTRY/$ACCOUNT/$SERVICE:$TAG" echo "Building for platform: $PLATFORM" echo "Local tag: $LOCAL_IMAGE_NAME" echo "Registry tag: $REGISTRY_IMAGE_NAME" # Build and/or push based on ACTION if [ "$ACTION" = "build" ]; then echo "🛠️ Building $SERVICE for $PLATFORM..." run_quiet "$SERVICE" build -- podman build \ --platform "$PLATFORM" \ $NO_CACHE \ $PROGRESS \ $DEBUG \ -t "$LOCAL_IMAGE_NAME" \ -t "$REGISTRY_IMAGE_NAME" \ -f "$CONTEXT/$DOCKERFILE" \ "$CONTEXT" if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/${SERVICE}.build.log" echo "❌ Failed to build $SERVICE" record_error "$SERVICE" build "❌ Failed to build $SERVICE" "$LOG_FILE" return 1 fi elif [ "$ACTION" = "push" ]; then echo "📤 Pushing $SERVICE to registry..." run_quiet "$SERVICE" push -- podman push "$REGISTRY_IMAGE_NAME" if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/${SERVICE}.push.log" echo "❌ Failed to push $SERVICE" record_error "$SERVICE" push "❌ Failed to push $SERVICE" "$LOG_FILE" return 1 fi else # Both build and push echo "🛠️ Building $SERVICE for $PLATFORM..." run_quiet "$SERVICE" build -- podman build \ --platform "$PLATFORM" \ $NO_CACHE \ $PROGRESS \ $DEBUG \ -t "$LOCAL_IMAGE_NAME" \ -t "$REGISTRY_IMAGE_NAME" \ -f "$CONTEXT/$DOCKERFILE" \ "$CONTEXT" if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/${SERVICE}.build.log" echo "❌ Failed to build $SERVICE" record_error "$SERVICE" build "❌ Failed to build $SERVICE" "$LOG_FILE" return 1 fi echo "📤 Pushing $SERVICE to registry..." run_quiet "$SERVICE" push -- podman push "$REGISTRY_IMAGE_NAME" if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/${SERVICE}.push.log" echo "❌ Failed to push $SERVICE" record_error "$SERVICE" push "❌ Failed to push $SERVICE" "$LOG_FILE" return 1 fi fi } # If no arguments are provided, process all services if [ $# -eq 0 ]; then SERVICES=() while IFS= read -r line; do SERVICES+=("$line") done < <(yq e '.services | keys | .[]' compose_dev.yaml | grep -E '^(nginx|eveai_|prometheus|grafana)') else SERVICES=("$@") fi # Handle base-only mode if [ "$BASE_ONLY" = "true" ]; then echo "🎯 Base-only mode: Building only base image" build_base_image echo -e "\033[32m✅ Base image build completed!\033[0m" exit 0 fi # Build base image if requested if should_build_base; then build_base_image echo "" # Empty line for readability fi echo "Using simplified AMD64-only approach for maximum compatibility..." echo "Images will be tagged as: $REGISTRY/$ACCOUNT/[service]:$TAG" # Reorder to ensure nginx builds before eveai_* if both are present HAS_NGINX=false HAS_APPS=false for S in "${SERVICES[@]}"; do if [[ "$S" == "nginx" ]]; then HAS_NGINX=true; fi if [[ "$S" == eveai_* ]]; then HAS_APPS=true; fi done if $HAS_NGINX && $HAS_APPS; then ORDERED_SERVICES=("nginx") for S in "${SERVICES[@]}"; do if [[ "$S" != "nginx" ]]; then ORDERED_SERVICES+=("$S"); fi done SERVICES=("${ORDERED_SERVICES[@]}") fi # Loop through services for SERVICE in "${SERVICES[@]}"; do if [[ "$SERVICE" == "nginx" ]]; then run_quiet nginx copy-specialist-svgs -- ./copy_specialist_svgs.sh ../config ../nginx/static/assets if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/nginx.copy-specialist-svgs.log" echo "⚠️ copy_specialist_svgs.sh not found or failed" record_error nginx copy-specialist-svgs "⚠️ copy_specialist_svgs.sh not found or failed" "$LOG_FILE" fi run_quiet nginx rebuild-chat-client -- ./rebuild_chat_client.sh if [ $? -ne 0 ]; then LOG_FILE="$LOG_DIR/nginx.rebuild-chat-client.log" echo "❌ rebuild_chat_client.sh failed" record_error nginx rebuild-chat-client "❌ rebuild_chat_client.sh failed" "$LOG_FILE" fi MANIFEST_SRC="../nginx/static/dist/manifest.json" MANIFEST_DST_DIR="../config/static-manifest" MANIFEST_DST="$MANIFEST_DST_DIR/manifest.json" if [ ! -f "$MANIFEST_SRC" ]; then if $HAS_NGINX; then echo "⚠️ manifest.json not found at $MANIFEST_SRC yet. nginx should be built first in this run." else echo "❌ manifest.json not found at $MANIFEST_SRC. Please build nginx (assets) first." exit 1 fi fi mkdir -p "$MANIFEST_DST_DIR" if [ -f "$MANIFEST_SRC" ]; then cp -f "$MANIFEST_SRC" "$MANIFEST_DST" echo "📄 Staged manifest at $MANIFEST_DST" fi fi if [[ "$SERVICE" == "nginx" || "$SERVICE" == eveai_* || "$SERVICE" == "prometheus" || "$SERVICE" == "grafana" ]]; then if process_service "$SERVICE"; then echo "✅ Successfully processed $SERVICE" else echo "❌ Failed to process $SERVICE" ERROR_LINES+=("❌ Failed to process $SERVICE") EXIT_CODE=1 fi else echo "⏭️ Skipping $SERVICE as it's not nginx, prometheus, grafana or doesn't start with eveai_" fi done if [ ${#ERRORS[@]} -eq 0 ]; then echo -e "\033[32m✅ All specified services processed successfully!\033[0m" echo -e "\033[32m📦 Images are available locally and in registry\033[0m" else echo -e "\033[31m❌ One or more errors occurred during build/push\033[0m" # Reprint short failure lines (your concise messages) for LINE in "${ERROR_LINES[@]}"; do echo "$LINE" done echo "" echo "Details (see logs for full output):" for ITEM in "${ERRORS[@]}"; do SERVICE_STEP_MSG_LOG=$(echo "$ITEM") IFS='|' read -r SVC STEP LOGFILE MSG <<< "$SERVICE_STEP_MSG_LOG" echo "- Service: $SVC | Step: $STEP" echo " ↳ Log: $LOGFILE" done EXIT_CODE=1 fi # Always print finished timestamp echo -e "\033[32m🕐 Finished at $(date +"%d/%m/%Y %H:%M:%S")\033[0m" exit $EXIT_CODE