import atexit import logging import os from pathlib import Path from flask import Flask from flask_login import LoginManager from werkzeug.middleware.proxy_fix import ProxyFix from .core import ( LicenseService, MonitorEngine, RuntimeState, User, ensure_secret_key, load_license_key, ) from .routes import register_routes def configure_logging() -> None: logging.basicConfig( level=os.getenv("VFM_LOG_LEVEL", "INFO").upper(), format="%(asctime)s [%(levelname)s] %(message)s", ) def is_production_mode() -> bool: value = os.getenv("VFM_ENV", "").strip().lower() return value in {"prod", "production"} def env_flag(name: str, default: bool = False) -> bool: raw_value = os.getenv(name) if raw_value is None: return default return raw_value.strip().lower() in {"1", "true", "yes", "on"} def create_app(base_dir: str | Path | None = None, start_monitor: bool = True) -> Flask: configure_logging() resolved_base_dir = Path(base_dir) if base_dir else Path(__file__).resolve().parent.parent package_root = Path(__file__).resolve().parent.parent template_dir = resolved_base_dir / "templates" static_dir = resolved_base_dir / "static" if not template_dir.exists(): template_dir = package_root / "templates" if not static_dir.exists(): static_dir = package_root / "static" secret_path = resolved_base_dir / "app_secret.key" default_password = os.getenv("VFM_DEFAULT_PASSWORD", "admin") app = Flask( __name__, template_folder=str(template_dir), static_folder=str(static_dir), ) app.secret_key = os.getenv("VFM_APP_SECRET") or ensure_secret_key(secret_path) app.config.update( SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", SESSION_COOKIE_SECURE=env_flag("VFM_SESSION_COOKIE_SECURE", is_production_mode()), REMEMBER_COOKIE_HTTPONLY=True, REMEMBER_COOKIE_SAMESITE="Lax", MAX_CONTENT_LENGTH=int(os.getenv("VFM_MAX_CONTENT_LENGTH", str(1024 * 1024))), ) if env_flag("VFM_TRUST_PROXY"): app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) runtime_state = RuntimeState(resolved_base_dir, default_password) license_service = LicenseService( resolved_base_dir / "license.key", load_license_key(require_env=is_production_mode()), ) app.extensions["runtime_state"] = runtime_state app.extensions["license_service"] = license_service login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = "login" @login_manager.user_loader def load_user(user_id): return User(user_id) register_routes(app) @app.after_request def apply_security_headers(response): response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "SAMEORIGIN" response.headers["Referrer-Policy"] = "same-origin" response.headers["Cache-Control"] = "no-store" return response if start_monitor: monitor_engine = MonitorEngine(runtime_state, license_service) app.extensions["monitor_engine"] = monitor_engine monitor_engine.start() atexit.register(monitor_engine.stop) return app