From eed80d3b800d2cb6f5b0363cba16b90e66012a14 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Mon, 16 Mar 2026 17:41:40 +0000 Subject: [PATCH] chore: auto-commit 2026-03-16 17:41 --- app/core/app.py | 12 +- app/core/config.py | 5 +- fix_vps_complete.sh | 343 ++++++++++++++++++++++++++++++++++++++++ test_fix/env.test.txt | 29 ++++ test_fix/test_config.py | 36 +++++ 5 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 fix_vps_complete.sh create mode 100644 test_fix/env.test.txt create mode 100644 test_fix/test_config.py diff --git a/app/core/app.py b/app/core/app.py index c645df7..eb2cced 100644 --- a/app/core/app.py +++ b/app/core/app.py @@ -15,8 +15,18 @@ from app.modules.admin.controller import router as admin_router from app.modules.oauth2 import oauth_router +# 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 logging.INFO, + level=logging.DEBUG if settings.debug else log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) diff --git a/app/core/config.py b/app/core/config.py index 5a6ac24..3478e8e 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -12,6 +12,9 @@ class Settings(BaseSettings): debug: bool = False title: str = "Mock API Server" version: str = "1.0.0" + log_level: str = "INFO" + host: str = "0.0.0.0" + port: int = 8000 # Admin authentication admin_username: str = "admin" @@ -72,7 +75,7 @@ class Settings(BaseSettings): # Return as-is for other types (should be list) return v - model_config = ConfigDict(env_file=".env") + model_config = ConfigDict(env_file=".env", extra="allow") settings = Settings() diff --git a/fix_vps_complete.sh b/fix_vps_complete.sh new file mode 100644 index 0000000..2a5af22 --- /dev/null +++ b/fix_vps_complete.sh @@ -0,0 +1,343 @@ +#!/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 \ No newline at end of file diff --git a/test_fix/env.test.txt b/test_fix/env.test.txt new file mode 100644 index 0000000..24fd845 --- /dev/null +++ b/test_fix/env.test.txt @@ -0,0 +1,29 @@ +# Mock API Configuration +# Generated on test + +# Database Configuration +DATABASE_URL=sqlite+aiosqlite:///./mockapi.db + +# Admin Authentication +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin123 + +# Security +SECRET_KEY=testsecretkey + +# Application Settings +DEBUG=False +LOG_LEVEL=INFO + +# OAuth2 Settings +OAUTH2_ISSUER=http://localhost:8000 +OAUTH2_ACCESS_TOKEN_EXPIRE_MINUTES=30 +OAUTH2_REFRESH_TOKEN_EXPIRE_DAYS=7 +OAUTH2_AUTHORIZATION_CODE_EXPIRE_MINUTES=10 +OAUTH2_SUPPORTED_GRANT_TYPES=["authorization_code", "client_credentials", "refresh_token"] +OAUTH2_SUPPORTED_SCOPES=["openid", "profile", "email", "api:read", "api:write"] +OAUTH2_PKCE_REQUIRED=false + +# Server Settings +HOST=0.0.0.0 +PORT=8000 \ No newline at end of file diff --git a/test_fix/test_config.py b/test_fix/test_config.py new file mode 100644 index 0000000..ea6e8ef --- /dev/null +++ b/test_fix/test_config.py @@ -0,0 +1,36 @@ +import os +import sys +import json + +# Add parent directory to path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Set environment variable to use our test .env +os.environ['ENV_FILE'] = os.path.join(os.path.dirname(__file__), 'env.test.txt') + +print(f"Using env file: {os.environ['ENV_FILE']}") + +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) \ No newline at end of file