#!/usr/bin/env bash # ------------------------------------------------------------- # Staging Deploy & Orchestration Helper for NexusReader # ------------------------------------------------------------- set -e NEXUS_ONLY=false for arg in "$@"; do case $arg in --nexus-only|-n) NEXUS_ONLY=true ;; esac done ENV_FILE=".env.stage" TEMPLATE_FILE=".env.stage.template" COMPOSE_FILE="docker-compose.stage.yml" echo "๐Ÿ Starting staging environment orchestration..." if [ "$NEXUS_ONLY" = true ]; then echo "โ„น๏ธ Mode: --nexus-only (only the web/nexus application container will be modified)" fi # 1. Create .env.stage if it doesn't exist if [ ! -f "$ENV_FILE" ]; then if [ -f "$TEMPLATE_FILE" ]; then echo "๐Ÿ“„ Creating $ENV_FILE from $TEMPLATE_FILE..." cp "$TEMPLATE_FILE" "$ENV_FILE" else echo "โŒ Error: Template file $TEMPLATE_FILE not found." exit 1 fi fi # 2. Check and generate secure random passwords for placeholders if grep -q "CHANGE_ME_TO_STRONG_PASSWORD" "$ENV_FILE"; then echo "๐Ÿ” Generating secure random passwords in $ENV_FILE..." PG_PASS=$(openssl rand -hex 16) NEO_PASS=$(openssl rand -hex 16) # Use standard sed compatible with Linux sed -i "s/POSTGRES_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD/POSTGRES_PASSWORD=$PG_PASS/g" "$ENV_FILE" sed -i "s/NEO4J_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD/NEO4J_PASSWORD=$NEO_PASS/g" "$ENV_FILE" fi if grep -q "CHANGE_ME_TO_SECURE_ADMIN_PASSWORD" "$ENV_FILE"; then echo "๐Ÿ” Generating secure admin seed password in $ENV_FILE..." ADMIN_PASS=$(openssl rand -hex 16) sed -i "s/NEXUS_ADMIN_PASSWORD=CHANGE_ME_TO_SECURE_ADMIN_PASSWORD/NEXUS_ADMIN_PASSWORD=$ADMIN_PASS/g" "$ENV_FILE" fi if grep -q "^QDRANT_API_KEY=$" "$ENV_FILE" || grep -q "^QDRANT_API_KEY=[[:space:]]*$" "$ENV_FILE"; then echo "๐Ÿ” Generating secure random Qdrant API key in $ENV_FILE..." QD_KEY=$(openssl rand -hex 16) sed -i "s/^QDRANT_API_KEY=.*/QDRANT_API_KEY=$QD_KEY/g" "$ENV_FILE" fi # Load staging variables for local execution context (needed for ports/migrations) # Clean up carriage returns just in case POSTGRES_USER=$(grep "^POSTGRES_USER=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') POSTGRES_DB=$(grep "^POSTGRES_DB=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') POSTGRES_PORT=$(grep "^POSTGRES_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') WEB_PORT=$(grep "^WEB_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') QDRANT_HTTP_PORT=$(grep "^QDRANT_HTTP_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') NEO4J_HTTP_PORT=$(grep "^NEO4J_HTTP_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r') # Fallbacks in case env parsing is empty POSTGRES_PORT=${POSTGRES_PORT:-5438} WEB_PORT=${WEB_PORT:-5080} # 3. Stop any conflicting Docker Compose environments if [ "$NEXUS_ONLY" = true ]; then echo "๐Ÿงน Stopping and removing only the web (nexus) container..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" stop web || true docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" rm -f web || true else echo "๐Ÿงน Stopping existing containers..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down --remove-orphans || true docker compose down --remove-orphans 2>/dev/null || true fi # 4. Build and start containers if [ "$NEXUS_ONLY" = true ]; then echo "๐Ÿš€ Building and restarting only the web (nexus) container..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --build web else echo "๐Ÿš€ Building and starting staging containers..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --build fi # 5. Wait for Database to be healthy echo "โณ Waiting for database (nexus-db-stage) to become healthy..." MAX_ATTEMPTS=30 attempt=0 until [ "$(docker inspect --format='{{json .State.Health.Status}}' nexus-db-stage 2>/dev/null)" == "\"healthy\"" ]; do sleep 2 attempt=$((attempt + 1)) if [ $attempt -ge $MAX_ATTEMPTS ]; then echo "โŒ Timeout: Database container never became healthy." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs db exit 1 fi done echo "โœ… Database is healthy!" # 6. Apply Entity Framework migrations echo "๐Ÿ”„ Applying EF Core migrations to staging database on port $POSTGRES_PORT..." export ConnectionStrings__PostgresConnection="Host=127.0.0.1;Port=$POSTGRES_PORT;Database=$POSTGRES_DB;Username=$POSTGRES_USER;Password=$POSTGRES_PASSWORD" dotnet ef database update --project src/NexusReader.Data --startup-project src/NexusReader.Web --no-build # 7. Wait for Web Application to respond echo "โณ Waiting for Web Application to start on http://localhost:$WEB_PORT/health..." MAX_WEB_ATTEMPTS=30 web_attempt=0 until curl -s -f "http://localhost:$WEB_PORT/health" >/dev/null; do sleep 2 web_attempt=$((web_attempt + 1)) if [ $web_attempt -ge $MAX_WEB_ATTEMPTS ]; then echo "โš ๏ธ Warning: Web app is not responding yet on http://localhost:$WEB_PORT/health, but let's check logs..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs web break fi done echo "๐ŸŽ‰ Staging environment is ready!" echo "--------------------------------------------------------" echo "๐ŸŒ Web Application: http://localhost:$WEB_PORT" echo "๐Ÿ—„๏ธ PostgreSQL Port: $POSTGRES_PORT" echo "๐Ÿ”Ž Neo4j Console: http://localhost:$NEO4J_HTTP_PORT" echo "๐Ÿ“Š Qdrant Service: http://localhost:$QDRANT_HTTP_PORT" echo "--------------------------------------------------------"