Aniworld/src/server/config/production.py
Lukas 1637835fe6 Task 11: Implement Deployment and Configuration
- 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.
2025-10-22 10:28:37 +02:00

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()