chore: auto-commit 2026-03-16 17:41
This commit is contained in:
parent
2e9a48b088
commit
eed80d3b80
5 changed files with 423 additions and 2 deletions
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
343
fix_vps_complete.sh
Normal file
343
fix_vps_complete.sh
Normal file
|
|
@ -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
|
||||
29
test_fix/env.test.txt
Normal file
29
test_fix/env.test.txt
Normal file
|
|
@ -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
|
||||
36
test_fix/test_config.py
Normal file
36
test_fix/test_config.py
Normal file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue