- Add production.py with security hardening and performance optimizations - Required environment variables for security (JWT, passwords, database) - Database connection pooling for PostgreSQL/MySQL - Security configurations and allowed hosts - Production logging and rotation settings - API rate limiting and performance tuning - Add development.py with relaxed settings for local development - Defaults for development (SQLite, debug logging, auto-reload) - Higher rate limits and longer session timeouts - Dev credentials for easy local setup - Development database defaults - Add environment configuration loader (__init__.py) - Automatic environment detection - Factory functions for lazy loading settings - Proper environment validation - Add startup scripts (start.sh) - Bash script for starting application in any environment - Conda environment validation - Automatic directory creation - Environment file generation - Database initialization - Development vs production startup modes - Add setup script (setup.py) - Python setup automation for environment initialization - Dependency installation - Environment file generation - Database initialization - Comprehensive validation and error handling - Update requirements.txt with psutil dependency All configurations follow project coding standards and include comprehensive documentation, type hints, and error handling.
235 lines
8.4 KiB
Python
235 lines
8.4 KiB
Python
"""
|
|
Production environment configuration for Aniworld application.
|
|
|
|
This module provides production-specific settings including security hardening,
|
|
performance optimizations, and operational configurations.
|
|
|
|
Environment Variables:
|
|
JWT_SECRET_KEY: Secret key for JWT token signing (REQUIRED)
|
|
PASSWORD_SALT: Salt for password hashing (REQUIRED)
|
|
DATABASE_URL: Production database connection string
|
|
LOG_LEVEL: Logging level (default: WARNING)
|
|
CORS_ORIGINS: Comma-separated list of allowed CORS origins
|
|
API_RATE_LIMIT: API rate limit per minute (default: 60)
|
|
WORKERS: Number of Uvicorn worker processes (default: 4)
|
|
WORKER_TIMEOUT: Worker timeout in seconds (default: 120)
|
|
"""
|
|
|
|
from typing import List
|
|
|
|
from pydantic import Field, validator
|
|
from pydantic_settings import BaseSettings
|
|
|
|
|
|
class ProductionSettings(BaseSettings):
|
|
"""Production environment configuration settings."""
|
|
|
|
# ============================================================================
|
|
# Security Settings
|
|
# ============================================================================
|
|
|
|
jwt_secret_key: str = Field(..., env="JWT_SECRET_KEY")
|
|
"""Secret key for JWT token signing. MUST be set in production."""
|
|
|
|
password_salt: str = Field(..., env="PASSWORD_SALT")
|
|
"""Salt for password hashing. MUST be set in production."""
|
|
|
|
master_password_hash: str = Field(..., env="MASTER_PASSWORD_HASH")
|
|
"""Hash of the master password for authentication."""
|
|
|
|
allowed_hosts: List[str] = Field(
|
|
default=["*"], env="ALLOWED_HOSTS"
|
|
)
|
|
"""List of allowed hostnames for CORS and security checks."""
|
|
|
|
cors_origins: str = Field(default="", env="CORS_ORIGINS")
|
|
"""Comma-separated list of allowed CORS origins."""
|
|
|
|
# ============================================================================
|
|
# Database Settings
|
|
# ============================================================================
|
|
|
|
database_url: str = Field(
|
|
default="postgresql://user:password@localhost/aniworld",
|
|
env="DATABASE_URL"
|
|
)
|
|
"""Database connection URL. Defaults to PostgreSQL for production."""
|
|
|
|
database_pool_size: int = Field(default=20, env="DATABASE_POOL_SIZE")
|
|
"""Database connection pool size."""
|
|
|
|
database_max_overflow: int = Field(default=10, env="DATABASE_MAX_OVERFLOW")
|
|
"""Maximum overflow connections for database pool."""
|
|
|
|
database_pool_recycle: int = Field(
|
|
default=3600, env="DATABASE_POOL_RECYCLE"
|
|
)
|
|
"""Recycle database connections every N seconds."""
|
|
|
|
# ============================================================================
|
|
# API Settings
|
|
# ============================================================================
|
|
|
|
api_rate_limit: int = Field(default=60, env="API_RATE_LIMIT")
|
|
"""API rate limit per minute per IP address."""
|
|
|
|
api_timeout: int = Field(default=30, env="API_TIMEOUT")
|
|
"""API request timeout in seconds."""
|
|
|
|
# ============================================================================
|
|
# Logging Settings
|
|
# ============================================================================
|
|
|
|
log_level: str = Field(default="WARNING", env="LOG_LEVEL")
|
|
"""Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)."""
|
|
|
|
log_file: str = Field(default="logs/production.log", env="LOG_FILE")
|
|
"""Path to production log file."""
|
|
|
|
log_rotation_size: int = Field(default=10_485_760, env="LOG_ROTATION_SIZE")
|
|
"""Log file rotation size in bytes (default: 10MB)."""
|
|
|
|
log_retention_days: int = Field(default=30, env="LOG_RETENTION_DAYS")
|
|
"""Number of days to retain log files."""
|
|
|
|
# ============================================================================
|
|
# Performance Settings
|
|
# ============================================================================
|
|
|
|
workers: int = Field(default=4, env="WORKERS")
|
|
"""Number of Uvicorn worker processes."""
|
|
|
|
worker_timeout: int = Field(default=120, env="WORKER_TIMEOUT")
|
|
"""Worker timeout in seconds."""
|
|
|
|
max_request_size: int = Field(default=104_857_600, env="MAX_REQUEST_SIZE")
|
|
"""Maximum request body size in bytes (default: 100MB)."""
|
|
|
|
session_timeout_hours: int = Field(default=24, env="SESSION_TIMEOUT_HOURS")
|
|
"""Session timeout in hours."""
|
|
|
|
# ============================================================================
|
|
# Provider Settings
|
|
# ============================================================================
|
|
|
|
default_provider: str = Field(
|
|
default="aniworld.to", env="DEFAULT_PROVIDER"
|
|
)
|
|
"""Default content provider."""
|
|
|
|
provider_timeout: int = Field(default=30, env="PROVIDER_TIMEOUT")
|
|
"""Provider request timeout in seconds."""
|
|
|
|
provider_retries: int = Field(default=3, env="PROVIDER_RETRIES")
|
|
"""Number of retry attempts for provider requests."""
|
|
|
|
# ============================================================================
|
|
# Download Settings
|
|
# ============================================================================
|
|
|
|
max_concurrent_downloads: int = Field(
|
|
default=3, env="MAX_CONCURRENT_DOWNLOADS"
|
|
)
|
|
"""Maximum concurrent downloads."""
|
|
|
|
download_timeout: int = Field(default=3600, env="DOWNLOAD_TIMEOUT")
|
|
"""Download timeout in seconds (default: 1 hour)."""
|
|
|
|
# ============================================================================
|
|
# Application Paths
|
|
# ============================================================================
|
|
|
|
anime_directory: str = Field(..., env="ANIME_DIRECTORY")
|
|
"""Directory where anime is stored."""
|
|
|
|
temp_directory: str = Field(default="/tmp/aniworld", env="TEMP_DIRECTORY")
|
|
"""Temporary directory for downloads and cache."""
|
|
|
|
# ============================================================================
|
|
# Validators
|
|
# ============================================================================
|
|
|
|
@validator("log_level")
|
|
@classmethod
|
|
def validate_log_level(cls, v: str) -> str:
|
|
"""Validate log level is valid."""
|
|
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
|
if v.upper() not in valid_levels:
|
|
raise ValueError(
|
|
f"Invalid log level '{v}'. Must be one of: {valid_levels}"
|
|
)
|
|
return v.upper()
|
|
|
|
@validator("database_url")
|
|
@classmethod
|
|
def validate_database_url(cls, v: str) -> str:
|
|
"""Validate database URL is set and not SQLite."""
|
|
if not v or v.startswith("sqlite"):
|
|
raise ValueError(
|
|
"Production database must not use SQLite. "
|
|
"Use PostgreSQL or MySQL instead."
|
|
)
|
|
return v
|
|
|
|
@validator("cors_origins")
|
|
@classmethod
|
|
def parse_cors_origins(cls, v: str) -> str:
|
|
"""Parse comma-separated CORS origins."""
|
|
if not v:
|
|
return ""
|
|
return v
|
|
|
|
# ============================================================================
|
|
# Configuration
|
|
# ============================================================================
|
|
|
|
class Config:
|
|
"""Pydantic config."""
|
|
|
|
env_file = ".env.production"
|
|
extra = "ignore"
|
|
case_sensitive = False
|
|
|
|
# ============================================================================
|
|
# Properties
|
|
# ============================================================================
|
|
|
|
@property
|
|
def parsed_cors_origins(self) -> List[str]:
|
|
"""Get parsed CORS origins as list."""
|
|
if not self.cors_origins:
|
|
return ["http://localhost", "http://127.0.0.1"]
|
|
return [origin.strip() for origin in self.cors_origins.split(",")]
|
|
|
|
@property
|
|
def is_production(self) -> bool:
|
|
"""Check if running in production mode."""
|
|
return True
|
|
|
|
@property
|
|
def debug_enabled(self) -> bool:
|
|
"""Check if debug mode is enabled."""
|
|
return False
|
|
|
|
@property
|
|
def reload_enabled(self) -> bool:
|
|
"""Check if auto-reload is enabled."""
|
|
return False
|
|
|
|
|
|
def get_production_settings() -> ProductionSettings:
|
|
"""
|
|
Get production settings instance.
|
|
|
|
This is a factory function that should be called when settings are needed,
|
|
rather than instantiating at module level to avoid requiring all
|
|
environment variables at import time.
|
|
|
|
Returns:
|
|
ProductionSettings instance configured from environment variables
|
|
|
|
Raises:
|
|
ValidationError: If required environment variables are missing
|
|
"""
|
|
return ProductionSettings()
|