#!/bin/bash # Automated deployment for Remote Access API + Guacamole # with automatic secure administrator generation set -e # Exit on error SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" echo "==========================================" echo " Remote Access API Deployment" echo "==========================================" echo "" # Output colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[OK]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Check required commands check_requirements() { log_info "Checking requirements..." if ! command -v docker &> /dev/null; then log_error "Docker not found! Please install Docker first." exit 1 fi if ! command -v python3 &> /dev/null; then log_error "Python 3 not found! Please install Python 3." exit 1 fi if ! docker compose version &> /dev/null; then log_error "Docker Compose V2 not found! Please install Docker Compose V2." exit 1 fi log_success "All requirements met" } # Load environment variables load_env() { log_info "Loading environment variables..." if [ ! -f ".env" ] && [ ! -f "production.env" ]; then log_error "No .env or production.env file found!" log_error "Please create one from encryption.env.example or production.env" exit 1 fi # Use production.env by default ENV_FILE=".env" if [ -f "production.env" ]; then ENV_FILE="production.env" log_info "Using production.env" fi # Load variables set -a source "$ENV_FILE" set +a log_success "Environment loaded from $ENV_FILE" } # Check critical passwords check_critical_passwords() { log_info "Checking critical passwords..." local has_issues=0 # Check REDIS_PASSWORD if [ -z "$REDIS_PASSWORD" ] || [ "$REDIS_PASSWORD" == "redis_pass" ]; then log_error "REDIS_PASSWORD is not set or using default value!" log_error "Set a secure password in $ENV_FILE" has_issues=1 fi # Check POSTGRES_PASSWORD if [ -z "$POSTGRES_PASSWORD" ] || [ "$POSTGRES_PASSWORD" == "guacamole_pass" ]; then log_error "POSTGRES_PASSWORD is not set or using default value!" log_error "Set a secure password in $ENV_FILE" has_issues=1 fi # Check SYSTEM_ADMIN credentials if [ -z "$SYSTEM_ADMIN_USERNAME" ] || [ -z "$SYSTEM_ADMIN_PASSWORD" ]; then log_error "SYSTEM_ADMIN_USERNAME and SYSTEM_ADMIN_PASSWORD must be set!" log_error "Please update your $ENV_FILE file" has_issues=1 fi if [ $has_issues -eq 1 ]; then log_error "" log_error "Critical passwords are missing or insecure!" log_error "Update the following in $ENV_FILE:" log_error " - REDIS_PASSWORD=" log_error " - POSTGRES_PASSWORD=" log_error " - SYSTEM_ADMIN_PASSWORD=" log_error "" log_error "Generate secure passwords:" log_error " openssl rand -base64 32" exit 1 fi log_success "All critical passwords are set" } # Auto-generate admin if password is not default generate_admin_sql() { log_info "Checking admin credentials..." # Check if default password is used if [ "$SYSTEM_ADMIN_PASSWORD" == "guacadmin" ] || \ [ "$SYSTEM_ADMIN_PASSWORD" == "guacadmin_change_in_production" ] || \ [[ "$SYSTEM_ADMIN_PASSWORD" == *"CHANGE_ME"* ]]; then log_warning "Default or placeholder password detected!" log_warning "Username: $SYSTEM_ADMIN_USERNAME" log_warning "Password: $SYSTEM_ADMIN_PASSWORD" log_warning "" log_warning "This is INSECURE for production!" log_warning "Using default 002-create-admin-user.sql" log_warning "" read -p "Continue anyway? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "Deployment cancelled. Please update your credentials." exit 1 fi return fi log_success "Custom password detected - generating secure admin SQL" log_info "Username: $SYSTEM_ADMIN_USERNAME" log_info "Password length: ${#SYSTEM_ADMIN_PASSWORD} characters" # Create backup of original SQL (if not already created) if [ -f "002-create-admin-user.sql" ] && [ ! -f "002-create-admin-user-DEFAULT-BACKUP.sql" ]; then log_info "Creating backup of default SQL..." cp 002-create-admin-user.sql 002-create-admin-user-DEFAULT-BACKUP.sql log_success "Backup created: 002-create-admin-user-DEFAULT-BACKUP.sql" fi # Generate new SQL log_info "Generating SQL with custom password..." python3 generate_guacamole_user.py \ --username "$SYSTEM_ADMIN_USERNAME" \ --password "$SYSTEM_ADMIN_PASSWORD" \ --admin \ --verify \ > 002-create-admin-user-GENERATED.sql if [ $? -ne 0 ]; then log_error "Failed to generate SQL!" exit 1 fi # Replace SQL mv 002-create-admin-user-GENERATED.sql 002-create-admin-user.sql log_success "Admin SQL generated and applied" log_info "File: 002-create-admin-user.sql (auto-generated)" } # Validate docker-compose.yml check_compose_file() { log_info "Validating docker-compose.yml..." if docker compose config > /dev/null 2>&1; then log_success "docker-compose.yml is valid" else log_error "Invalid docker-compose.yml!" docker compose config exit 1 fi } # Start containers start_containers() { log_info "Starting containers..." # Stop existing containers (if any) docker compose down 2>/dev/null || true # Start docker compose up -d if [ $? -eq 0 ]; then log_success "Containers started successfully" else log_error "Failed to start containers!" exit 1 fi } # Wait for services to be ready wait_for_services() { log_info "Waiting for services to be ready..." # Wait for PostgreSQL log_info "Waiting for PostgreSQL..." for i in {1..30}; do if docker compose exec -T postgres pg_isready -U guacamole_user &>/dev/null; then log_success "PostgreSQL is ready" break fi if [ $i -eq 30 ]; then log_error "PostgreSQL failed to start!" docker compose logs postgres exit 1 fi sleep 1 done # Wait for Guacamole log_info "Waiting for Guacamole..." local guacamole_ready=0 for i in {1..60}; do if curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/guacamole/ | grep -q "200\|302"; then log_success "Guacamole is ready" guacamole_ready=1 break fi sleep 2 done if [ $guacamole_ready -eq 0 ]; then log_warning "Guacamole might not be ready yet (timeout after 120s)" log_info "Check logs: docker compose logs guacamole" fi # Wait for Redis log_info "Waiting for Redis..." local redis_ready=0 for i in {1..20}; do if docker compose exec -T redis redis-cli -a "$REDIS_PASSWORD" ping &>/dev/null | grep -q "PONG"; then log_success "Redis is ready" redis_ready=1 break fi sleep 1 done if [ $redis_ready -eq 0 ]; then log_warning "Redis might not be ready yet (timeout after 20s)" fi # Wait for API log_info "Waiting for API..." local api_ready=0 for i in {1..45}; do if docker compose logs remote_access_api 2>&1 | grep -q "Application startup complete"; then log_info "API startup detected, checking health endpoint..." sleep 2 # Give it a moment to fully initialize # Check health endpoint if curl -s http://localhost:8000/api/health | grep -q '"overall_status":"ok"'; then log_success "API is ready and healthy" api_ready=1 break elif curl -s http://localhost:8000/api/health | grep -q '"overall_status"'; then log_warning "API is running but some components have issues" log_info "Check: curl http://localhost:8000/api/health | jq" api_ready=1 break fi fi sleep 2 done if [ $api_ready -eq 0 ]; then log_error "API failed to start properly (timeout after 90s)" log_info "Check logs: docker compose logs remote_access_api" log_info "Last 30 lines:" docker compose logs --tail=30 remote_access_api exit 1 fi } # Verify deployment verify_deployment() { log_info "Verifying deployment..." # Check that admin user was created ADMIN_CHECK=$(docker compose exec -T postgres psql -U guacamole_user -d guacamole_db -t -c \ "SELECT COUNT(*) FROM guacamole_user u JOIN guacamole_entity e ON u.entity_id = e.entity_id WHERE e.name = '$SYSTEM_ADMIN_USERNAME';" 2>/dev/null | tr -d ' ') if [ "$ADMIN_CHECK" == "1" ]; then log_success "Admin user '$SYSTEM_ADMIN_USERNAME' exists in database" else log_warning "Could not verify admin user in database" fi # Check API health endpoint log_info "Checking API health endpoint..." HEALTH_RESPONSE=$(curl -s http://localhost:8000/api/health) if echo "$HEALTH_RESPONSE" | grep -q '"overall_status":"ok"'; then log_success "API health check: OK" # Parse component statuses (if jq is available) if command -v jq &> /dev/null; then echo "$HEALTH_RESPONSE" | jq -r '.components | to_entries[] | " - \(.key): \(.value.status)"' 2>/dev/null || true fi elif echo "$HEALTH_RESPONSE" | grep -q '"overall_status"'; then local status=$(echo "$HEALTH_RESPONSE" | grep -o '"overall_status":"[^"]*"' | cut -d'"' -f4) log_warning "API health check: $status (some components have issues)" if command -v jq &> /dev/null; then echo "$HEALTH_RESPONSE" | jq -r '.components | to_entries[] | " - \(.key): \(.value.status)"' 2>/dev/null || true else log_info "Install 'jq' for detailed component status" fi else log_error "API health check failed!" log_info "Response: $HEALTH_RESPONSE" exit 1 fi # Check API logs for authentication if docker compose logs remote_access_api 2>&1 | grep -q "System token refreshed successfully\|authenticated with system credentials"; then log_success "API successfully authenticated with system credentials" else log_warning "Could not verify API system authentication in logs" log_info "This may be normal if using cached tokens" fi } # Print deployment summary print_summary() { echo "" echo "==========================================" echo " Deployment Complete!" echo "==========================================" echo "" log_success "Services are running" echo "" echo "Access URLs:" echo " - Guacamole UI: http://localhost:8080/guacamole/" echo " - API Health: http://localhost:8000/api/health" echo " - API Docs: http://localhost:8000/docs (if enabled)" echo "" echo "Admin Credentials:" echo " - Username: $SYSTEM_ADMIN_USERNAME" echo " - Password: ${SYSTEM_ADMIN_PASSWORD:0:3}***${SYSTEM_ADMIN_PASSWORD: -3} (length: ${#SYSTEM_ADMIN_PASSWORD})" echo "" echo "Useful Commands:" echo " - View logs: docker compose logs -f" echo " - API logs: docker compose logs -f remote_access_api" echo " - Check health: curl http://localhost:8000/api/health | jq" echo " - Stop: docker compose down" echo " - Restart: docker compose restart" echo "" if [ -f "002-create-admin-user-DEFAULT-BACKUP.sql" ]; then log_info "Original SQL backed up to: 002-create-admin-user-DEFAULT-BACKUP.sql" fi echo "" } # Main deployment flow main() { check_requirements load_env check_critical_passwords generate_admin_sql check_compose_file start_containers wait_for_services verify_deployment print_summary } # Run main