#!/bin/bash set -euo pipefail # Mosaic Stack - Docker Swarm Deployment Script # Usage: ./deploy-swarm.sh [stack-name] STACK_NAME="${1:-mosaic}" COMPOSE_FILE="docker-compose.swarm.yml" echo "🚀 Deploying Mosaic Stack to Docker Swarm..." echo "Stack name: $STACK_NAME" echo "Compose file: $COMPOSE_FILE" echo "" # Check if .env exists if [ ! -f .env ]; then echo "❌ Error: .env file not found" echo "📝 Run the setup wizard to create it:" echo " ./setup-wizard.sh" echo "" echo "Or copy from example:" echo " cp .env.example .env" echo " nano .env" exit 1 fi # Check required environment variables echo "🔍 Checking required environment variables..." missing_vars=() # Check critical variables for var in POSTGRES_PASSWORD JWT_SECRET OIDC_CLIENT_ID OIDC_CLIENT_SECRET ENCRYPTION_KEY BETTER_AUTH_SECRET; do if ! grep -q "^${var}=" .env || grep -q "^${var}=.*REPLACE" .env || [ -z "$(grep "^${var}=" .env | cut -d= -f2)" ]; then missing_vars+=("$var") fi done # Check AI provider configuration AI_PROVIDER=$(grep "^AI_PROVIDER=" .env 2>/dev/null | cut -d= -f2 || echo "ollama") if [ "$AI_PROVIDER" = "claude" ]; then if ! grep -q "^CLAUDE_API_KEY=" .env || grep -q "^CLAUDE_API_KEY=.*REPLACE" .env; then missing_vars+=("CLAUDE_API_KEY (required when AI_PROVIDER=claude)") fi elif [ "$AI_PROVIDER" = "openai" ]; then if ! grep -q "^OPENAI_API_KEY=" .env || grep -q "^OPENAI_API_KEY=.*REPLACE" .env; then missing_vars+=("OPENAI_API_KEY (required when AI_PROVIDER=openai)") fi fi if [ ${#missing_vars[@]} -gt 0 ]; then echo "❌ Missing or placeholder values for:" printf " - %s\n" "${missing_vars[@]}" echo "" echo "Run the setup wizard to configure:" echo " ./setup-wizard.sh" echo "" echo "Or manually edit .env" exit 1 fi echo "✅ All required variables configured" echo " AI Provider: $AI_PROVIDER" echo "" # Check if traefik-public network exists if ! docker network ls --filter name=traefik-public --format '{{.Name}}' | grep -q '^traefik-public$'; then echo "⚠️ traefik-public network not found. Creating it..." docker network create --driver=overlay traefik-public echo "✅ traefik-public network created" else echo "✅ traefik-public network already exists" fi # Check if images exist, offer to build if not echo "" echo "🔍 Checking if images are built..." IMAGES_MISSING=0 for img in mosaic-stack-postgres mosaic-stack-openbao mosaic-stack-api mosaic-stack-orchestrator mosaic-stack-web; do if ! docker images --format "{{.Repository}}" | grep -q "^${img}$"; then echo " ⚠️ Missing: $img" IMAGES_MISSING=1 fi done if [ $IMAGES_MISSING -eq 1 ]; then echo "" echo "❌ Some images are missing. Build them first:" echo " ./build-images.sh" echo "" read -p "Build images now? [Y/n]: " BUILD_NOW BUILD_NOW=${BUILD_NOW:-Y} if [[ $BUILD_NOW =~ ^[Yy]$ ]]; then ./build-images.sh || exit 1 else echo "Aborting deployment. Build images first." exit 1 fi else echo "✅ All images are built" fi # Deploy the stack echo "" echo "📦 Deploying stack..." docker stack deploy -c $COMPOSE_FILE --with-registry-auth $STACK_NAME echo "" echo "✅ Stack deployed successfully!" echo "" echo "📊 Stack status:" docker stack ps $STACK_NAME --format "table {{.Name}}\t{{.CurrentState}}\t{{.Error}}" echo "" echo "🔍 To check stack services:" echo " docker stack services $STACK_NAME" echo "" echo "🔍 To check stack logs:" echo " docker service logs ${STACK_NAME}_api" echo " docker service logs ${STACK_NAME}_web" echo " docker service logs ${STACK_NAME}_postgres" echo "" echo "🗑️ To remove the stack:" echo " docker stack rm $STACK_NAME"