""" Configuration management for StarPunk Loads settings from environment variables and .env file """ import os from pathlib import Path from dotenv import load_dotenv def load_config(app, config_override=None): """ Load configuration into Flask app Args: app: Flask application instance config_override: Optional dict to override config values """ # Load .env file load_dotenv() # Site configuration app.config["SITE_URL"] = os.getenv("SITE_URL", "http://localhost:5000") app.config["SITE_NAME"] = os.getenv("SITE_NAME", "StarPunk") app.config["SITE_AUTHOR"] = os.getenv("SITE_AUTHOR", "Unknown") app.config["SITE_DESCRIPTION"] = os.getenv( "SITE_DESCRIPTION", "A minimal IndieWeb CMS" ) # Authentication app.config["ADMIN_ME"] = os.getenv("ADMIN_ME") app.config["SESSION_SECRET"] = os.getenv("SESSION_SECRET") app.config["SESSION_LIFETIME"] = int(os.getenv("SESSION_LIFETIME", "30")) app.config["INDIELOGIN_URL"] = os.getenv("INDIELOGIN_URL", "https://indielogin.com") # Validate required configuration if not app.config["SESSION_SECRET"]: raise ValueError( "SESSION_SECRET must be set in .env file. " 'Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"' ) # Flask secret key (uses SESSION_SECRET by default) app.config["SECRET_KEY"] = os.getenv( "FLASK_SECRET_KEY", app.config["SESSION_SECRET"] ) # Data paths app.config["DATA_PATH"] = Path(os.getenv("DATA_PATH", "./data")) app.config["NOTES_PATH"] = Path(os.getenv("NOTES_PATH", "./data/notes")) app.config["DATABASE_PATH"] = Path(os.getenv("DATABASE_PATH", "./data/starpunk.db")) # Flask environment app.config["ENV"] = os.getenv("FLASK_ENV", "development") app.config["DEBUG"] = os.getenv("FLASK_DEBUG", "1") == "1" # Logging app.config["LOG_LEVEL"] = os.getenv("LOG_LEVEL", "INFO") # Development mode configuration app.config["DEV_MODE"] = os.getenv("DEV_MODE", "false").lower() == "true" app.config["DEV_ADMIN_ME"] = os.getenv("DEV_ADMIN_ME", "") # Application version (use __version__ from package) from starpunk import __version__ app.config["VERSION"] = os.getenv("VERSION", __version__) # RSS feed configuration app.config["FEED_MAX_ITEMS"] = int(os.getenv("FEED_MAX_ITEMS", "50")) app.config["FEED_CACHE_SECONDS"] = int(os.getenv("FEED_CACHE_SECONDS", "300")) # Apply overrides if provided if config_override: app.config.update(config_override) # Convert path strings to Path objects (in case overrides provided strings) if isinstance(app.config["DATA_PATH"], str): app.config["DATA_PATH"] = Path(app.config["DATA_PATH"]) if isinstance(app.config["NOTES_PATH"], str): app.config["NOTES_PATH"] = Path(app.config["NOTES_PATH"]) if isinstance(app.config["DATABASE_PATH"], str): app.config["DATABASE_PATH"] = Path(app.config["DATABASE_PATH"]) # Validate configuration validate_config(app) # Ensure data directories exist app.config["DATA_PATH"].mkdir(parents=True, exist_ok=True) app.config["NOTES_PATH"].mkdir(parents=True, exist_ok=True) def validate_config(app): """ Validate application configuration on startup Ensures required configuration is present based on mode (dev/production) and warns prominently if development mode is enabled. Args: app: Flask application instance Raises: ValueError: If required configuration is missing """ dev_mode = app.config.get("DEV_MODE", False) if dev_mode: # Prominently warn about development mode app.logger.warning( "=" * 60 + "\n" "WARNING: Development authentication enabled!\n" "This should NEVER be used in production.\n" "Set DEV_MODE=false for production deployments.\n" + "=" * 60 ) # Require DEV_ADMIN_ME in dev mode if not app.config.get("DEV_ADMIN_ME"): raise ValueError( "DEV_MODE=true requires DEV_ADMIN_ME to be set. " "Set DEV_ADMIN_ME=https://your-dev-identity.example.com in .env" ) else: # Production mode: ADMIN_ME is required if not app.config.get("ADMIN_ME"): raise ValueError( "Production mode requires ADMIN_ME to be set. " "Set ADMIN_ME=https://your-site.com in .env" )