#!/bin/bash # Complete fix script for MockAPI VPS deployment issues # Run this on the VPS as the same user who installed MockAPI set -e echo "=== MockAPI VPS Complete Fix Script ===" echo APP_DIR="/opt/mockapi" ENV_FILE="${APP_DIR}/.env" CONFIG_FILE="${APP_DIR}/app/core/config.py" APP_FILE="${APP_DIR}/app/core/app.py" # Function to print colored output print_status() { echo -e "\033[0;32m[+]\033[0m $1" } print_warning() { echo -e "\033[1;33m[!]\033[0m $1" } print_error() { echo -e "\033[0;31m[!]\033[0m $1" } # Step 0: Check we're in the right place print_status "Step 0: Checking environment" if [[ ! -d "$APP_DIR" ]]; then print_error "Application directory not found: $APP_DIR" exit 1 fi if [[ ! -f "$CONFIG_FILE" ]]; then print_error "Config file not found: $CONFIG_FILE" exit 1 fi cd "$APP_DIR" # Step 1: Backup original files print_status "Step 1: Backing up original files" BACKUP_DIR="${APP_DIR}/backup_$(date +%s)" mkdir -p "$BACKUP_DIR" cp "$CONFIG_FILE" "$BACKUP_DIR/config.py.backup" cp "$APP_FILE" "$BACKUP_DIR/app.py.backup" if [[ -f "$ENV_FILE" ]]; then cp "$ENV_FILE" "$BACKUP_DIR/env.backup" fi print_status "Backups created in: $BACKUP_DIR" # Step 2: Fix config.py - add missing fields and allow extra fields print_status "Step 2: Fixing config.py" # Check if log_level field already exists if ! grep -q "log_level:" "$CONFIG_FILE"; then print_status "Adding log_level, host, and port fields to config.py" # Insert after the version field line sed -i '/version: str = "1.0.0"/a\ log_level: str = "INFO"\n host: str = "0.0.0.0"\n port: int = 8000' "$CONFIG_FILE" else print_status "log_level field already exists" fi # Ensure model_config allows extra fields if grep -q 'model_config = ConfigDict(env_file=".env")' "$CONFIG_FILE"; then print_status "Updating model_config to allow extra fields" sed -i 's/model_config = ConfigDict(env_file=".env")/model_config = ConfigDict(env_file=".env", extra="allow")/' "$CONFIG_FILE" elif grep -q 'extra="allow"' "$CONFIG_FILE"; then print_status "model_config already allows extra fields" else print_warning "Could not find model_config line to update" fi # Step 3: Fix app.py - update logging configuration print_status "Step 3: Fixing app.py logging configuration" # Check if log_level_map already exists if ! grep -q "log_level_map" "$APP_FILE"; then print_status "Updating logging configuration in app.py" # Create a temporary file with the new logging config cat > /tmp/new_logging_config.py << 'EOF' # Convert log level string to logging constant log_level_map = { "DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING, "ERROR": logging.ERROR, "CRITICAL": logging.CRITICAL, } log_level = log_level_map.get(settings.log_level.upper(), logging.INFO) logging.basicConfig( level=logging.DEBUG if settings.debug else log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) EOF # Replace the old logging.basicConfig block # Find the line numbers of the old block START_LINE=$(grep -n "logging.basicConfig" "$APP_FILE" | head -1 | cut -d: -f1) if [[ -n "$START_LINE" ]]; then # Find the next line after the closing parenthesis END_LINE=$((START_LINE + 3)) # Check if we have the right pattern if sed -n "${START_LINE},${END_LINE}p" "$APP_FILE" | grep -q "logger = logging.getLogger"; then # Replace lines START_LINE to END_LINE with new config sed -i "${START_LINE},${END_LINE}d" "$APP_FILE" sed -i "${START_LINE}r /tmp/new_logging_config.py" "$APP_FILE" print_status "Updated logging configuration" else print_warning "Could not find exact logging block pattern, attempting manual replacement" # Fallback: replace the whole block we can identify sed -i '/logging.basicConfig/,/^logger = logging.getLogger/d' "$APP_FILE" sed -i "/import logging/a\\ # Convert log level string to logging constant\\ log_level_map = {\\ \"DEBUG\": logging.DEBUG,\\ \"INFO\": logging.INFO,\\ \"WARNING\": logging.WARNING,\\ \"ERROR\": logging.ERROR,\\ \"CRITICAL\": logging.CRITICAL,\\ }\\ log_level = log_level_map.get(settings.log_level.upper(), logging.INFO)\\ \\ logging.basicConfig(\\ level=logging.DEBUG if settings.debug else log_level,\\ format=\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\",\\ )\\ logger = logging.getLogger(__name__)" "$APP_FILE" fi else print_error "Could not find logging.basicConfig in app.py" fi rm /tmp/new_logging_config.py else print_status "Logging configuration already updated" fi # Step 4: Fix .env file format issues print_status "Step 4: Fixing .env file format issues" if [[ -f "$ENV_FILE" ]]; then # Fix OAUTH2_SUPPORTED_SCOPES to proper JSON format SCOPES_LINE=$(grep -i "^OAUTH2_SUPPORTED_SCOPES=" "$ENV_FILE" | head -1) if [[ -n "$SCOPES_LINE" ]]; then SCOPES_VALUE=$(echo "$SCOPES_LINE" | cut -d'=' -f2-) # Check if value is valid JSON array if ! python3 -c "import json; json.loads('$SCOPES_VALUE')" 2>/dev/null; then print_warning "OAUTH2_SUPPORTED_SCOPES value is not valid JSON: $SCOPES_VALUE" print_status "Converting to JSON array format" # Convert space-separated or comma-separated to JSON array CLEAN_VALUE=$(echo "$SCOPES_VALUE" | sed 's/"//g' | sed "s/'//g") JSON_ARRAY=$(echo "$CLEAN_VALUE" | python3 -c " import sys, json value = sys.stdin.read().strip() if ',' in value: items = [item.strip() for item in value.split(',')] else: items = [item.strip() for item in value.split()] print(json.dumps([item for item in items if item])) ") # Update .env file sed -i "s/^OAUTH2_SUPPORTED_SCOPES=.*$/OAUTH2_SUPPORTED_SCOPES=$JSON_ARRAY/" "$ENV_FILE" print_status "Updated OAUTH2_SUPPORTED_SCOPES to: $JSON_ARRAY" else print_status "OAUTH2_SUPPORTED_SCOPES already in valid JSON format" fi fi # Fix OAUTH2_SUPPORTED_GRANT_TYPES to proper JSON format GRANT_LINE=$(grep -i "^OAUTH2_SUPPORTED_GRANT_TYPES=" "$ENV_FILE" | head -1) if [[ -n "$GRANT_LINE" ]]; then GRANT_VALUE=$(echo "$GRANT_LINE" | cut -d'=' -f2-) # Check if value is valid JSON array if ! python3 -c "import json; json.loads('$GRANT_VALUE')" 2>/dev/null; then print_warning "OAUTH2_SUPPORTED_GRANT_TYPES value is not valid JSON: $GRANT_VALUE" print_status "Converting to JSON array format" # Convert space-separated or comma-separated to JSON array CLEAN_VALUE=$(echo "$GRANT_VALUE" | sed 's/"//g' | sed "s/'//g") JSON_ARRAY=$(echo "$CLEAN_VALUE" | python3 -c " import sys, json value = sys.stdin.read().strip() if ',' in value: items = [item.strip() for item in value.split(',')] else: items = [item.strip() for item in value.split()] print(json.dumps([item for item in items if item])) ") # Update .env file sed -i "s/^OAUTH2_SUPPORTED_GRANT_TYPES=.*$/OAUTH2_SUPPORTED_GRANT_TYPES=$JSON_ARRAY/" "$ENV_FILE" print_status "Updated OAUTH2_SUPPORTED_GRANT_TYPES to: $JSON_ARRAY" else print_status "OAUTH2_SUPPORTED_GRANT_TYPES already in valid JSON format" fi fi # Remove duplicate entries print_status "Checking for duplicate entries in .env" for FIELD in OAUTH2_SUPPORTED_SCOPES OAUTH2_SUPPORTED_GRANT_TYPES; do COUNT=$(grep -i "^${FIELD}=" "$ENV_FILE" | wc -l) if [[ $COUNT -gt 1 ]]; then print_warning "Found $COUNT entries for $FIELD, keeping only the first" FIRST_LINE=$(grep -i -n "^${FIELD}=" "$ENV_FILE" | head -1 | cut -d: -f1) # Create temp file without any of this field grep -v -i "^${FIELD}=" "$ENV_FILE" > "${ENV_FILE}.tmp" # Add back the first occurrence grep -i "^${FIELD}=" "$ENV_FILE" | head -1 >> "${ENV_FILE}.tmp" mv "${ENV_FILE}.tmp" "$ENV_FILE" fi done # Ensure required fields exist print_status "Ensuring all required fields exist in .env" if ! grep -q "^LOG_LEVEL=" "$ENV_FILE"; then echo "LOG_LEVEL=INFO" >> "$ENV_FILE" print_status "Added LOG_LEVEL=INFO" fi if ! grep -q "^HOST=" "$ENV_FILE"; then echo "HOST=0.0.0.0" >> "$ENV_FILE" print_status "Added HOST=0.0.0.0" fi if ! grep -q "^PORT=" "$ENV_FILE"; then echo "PORT=8000" >> "$ENV_FILE" print_status "Added PORT=8000" fi if ! grep -q "^OAUTH2_AUTHORIZATION_CODE_EXPIRE_MINUTES=" "$ENV_FILE"; then echo "OAUTH2_AUTHORIZATION_CODE_EXPIRE_MINUTES=10" >> "$ENV_FILE" print_status "Added OAUTH2_AUTHORIZATION_CODE_EXPIRE_MINUTES=10" fi if ! grep -q "^OAUTH2_PKCE_REQUIRED=" "$ENV_FILE"; then echo "OAUTH2_PKCE_REQUIRED=false" >> "$ENV_FILE" print_status "Added OAUTH2_PKCE_REQUIRED=false" fi # Update OAUTH2_ISSUER if port has changed CURRENT_PORT=$(grep "^PORT=" "$ENV_FILE" | cut -d'=' -f2) CURRENT_ISSUER=$(grep "^OAUTH2_ISSUER=" "$ENV_FILE" | cut -d'=' -f2) EXPECTED_ISSUER="http://localhost:${CURRENT_PORT}" if [[ "$CURRENT_ISSUER" != "$EXPECTED_ISSUER" ]]; then print_status "Updating OAUTH2_ISSUER to match port" sed -i "s|^OAUTH2_ISSUER=.*|OAUTH2_ISSUER=$EXPECTED_ISSUER|" "$ENV_FILE" fi else print_warning ".env file not found, skipping .env fixes" fi # Step 5: Test the configuration print_status "Step 5: Testing configuration import" cat > test_config.py << 'EOF' import os import sys import json # Add app directory to path sys.path.insert(0, os.getcwd()) try: print("Attempting to import config...") from app.core.config import settings print("✓ Config import successful!") print(f" DEBUG: {settings.debug}") print(f" LOG_LEVEL: {settings.log_level}") print(f" HOST: {settings.host}") print(f" PORT: {settings.port}") print(f" OAUTH2_SUPPORTED_SCOPES: {settings.oauth2_supported_scopes}") print(f" OAUTH2_SUPPORTED_GRANT_TYPES: {settings.oauth2_supported_grant_types}") print(f" Type of scopes: {type(settings.oauth2_supported_scopes)}") print(f" Type of grant types: {type(settings.oauth2_supported_grant_types)}") # Test JSON serialization print(f" Scopes as JSON: {json.dumps(settings.oauth2_supported_scopes)}") print("✓ All configuration tests passed!") except Exception as e: print(f"✗ Config import failed: {e}") import traceback traceback.print_exc() sys.exit(1) EOF if python3 test_config.py; then print_status "Configuration test passed!" rm test_config.py else print_error "Configuration test failed" print_warning "Check the error above. The .env file may still have issues." exit 1 fi # Step 6: Restart the service print_status "Step 6: Restarting MockAPI service" sudo systemctl restart mockapi sleep 5 if sudo systemctl is-active --quiet mockapi; then print_status "✓ Service is running successfully!" echo echo "Service status:" sudo systemctl status mockapi --no-pager -l echo print_status "Testing API endpoints..." if curl -f -s http://localhost:8000/health > /dev/null 2>&1; then print_status "✓ Health check passed: http://localhost:8000/health" echo echo "Access your MockAPI:" echo " Admin UI: http://localhost:8000/admin/login" echo " API Docs: http://localhost:8000/docs" echo " Health check: http://localhost:8000/health" else print_warning "Health check failed. Checking logs..." sudo journalctl -u mockapi -n 10 --no-pager fi else print_error "Service failed to start" echo print_warning "Checking service logs..." sudo journalctl -u mockapi -n 30 --no-pager echo print_warning "If the service still fails, check:" print_warning "1. The .env file format: ${ENV_FILE}" print_warning "2. File permissions in ${APP_DIR}" print_warning "3. Database file permissions" exit 1 fi echo print_status "=== Fix completed successfully! ===" echo echo "Backup files are in: $BACKUP_DIR" echo "If you encounter issues, you can restore from backup." echo