From 4bd4127bb5499374c44e06c3a10755acf62663d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD?= Date: Sat, 7 Feb 2026 13:51:52 +0300 Subject: [PATCH] profiler module moved from static config to environment dpendent config --- APP_PROFILER/config.ini | 11 +++++++ APP_PROFILER/database.py | 7 ++++- APP_PROFILER/main.py | 2 +- APP_PROFILER/requirements.txt | 1 + APP_PROFILER/utils/auth.py | 54 +++++++++++------------------------ APP_PROFILER/utils/config.py | 32 +++++++++++++++++++++ APP_UI/default.conf.template | 26 +++++++++++------ docker-compose.yml | 4 ++- 8 files changed, 88 insertions(+), 49 deletions(-) create mode 100644 APP_PROFILER/config.ini create mode 100644 APP_PROFILER/utils/config.py diff --git a/APP_PROFILER/config.ini b/APP_PROFILER/config.ini new file mode 100644 index 0000000..1c4206a --- /dev/null +++ b/APP_PROFILER/config.ini @@ -0,0 +1,11 @@ +[api] +# Secret key for JWT token verification. +# MUST match the key in APP_CORE/config.ini if not overridden by ENV. +secret_key = ovpmon-secret-change-me + +[profiler] +# Path to the profiler database relative to component root +db_path = ovpn_profiler.db + +[logging] +level = INFO diff --git a/APP_PROFILER/database.py b/APP_PROFILER/database.py index 41184bd..5331291 100644 --- a/APP_PROFILER/database.py +++ b/APP_PROFILER/database.py @@ -2,7 +2,12 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -SQLALCHEMY_DATABASE_URL = "sqlite:///./ovpn_profiler.db" +from utils.config import get_config_value + +# Support override via OVPMON_PROFILER_DB_PATH or config.ini +db_path = get_config_value('profiler', 'db_path', fallback='./ovpn_profiler.db') +SQLALCHEMY_DATABASE_URL = f"sqlite:///{db_path}" + engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} diff --git a/APP_PROFILER/main.py b/APP_PROFILER/main.py index 65d4e7b..5d211ba 100644 --- a/APP_PROFILER/main.py +++ b/APP_PROFILER/main.py @@ -42,4 +42,4 @@ def read_root(): return {"message": "Welcome to OpenVPN Profiler API"} if __name__ == "__main__": - uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True) + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/APP_PROFILER/requirements.txt b/APP_PROFILER/requirements.txt index bb00e47..7eec6e6 100644 --- a/APP_PROFILER/requirements.txt +++ b/APP_PROFILER/requirements.txt @@ -4,3 +4,4 @@ sqlalchemy psutil python-multipart jinja2 +pyjwt \ No newline at end of file diff --git a/APP_PROFILER/utils/auth.py b/APP_PROFILER/utils/auth.py index e907523..5ca5fe1 100644 --- a/APP_PROFILER/utils/auth.py +++ b/APP_PROFILER/utils/auth.py @@ -4,49 +4,29 @@ import os from fastapi import Header, HTTPException, status from pathlib import Path -# Load config from the main APP directory -CONFIG_FILE = Path(__file__).parent.parent.parent / 'APP' / 'config.ini' +from .config import get_config_value def get_secret_key(): - # Priority 1: Environment Variable - env_secret = os.getenv('OVPMON_SECRET_KEY') - if env_secret: - print("[AUTH] Using SECRET_KEY from environment variable") - return env_secret - - # Priority 2: Config file (multiple possible locations) - # Resolve absolute path to be sure - base_path = Path(__file__).resolve().parent.parent + # Use consistent OVPMON_API_SECRET_KEY as primary source + key = get_config_value('api', 'secret_key', fallback='ovpmon-secret-change-me') - config_locations = [ - base_path.parent / 'APP' / 'config.ini', # Brother directory (Local/Gitea structure) - base_path / 'APP' / 'config.ini', # Child directory - base_path / 'config.ini', # Same directory - Path('/opt/ovpmon/APP/config.ini'), # Common production path 1 - Path('/opt/ovpmon/config.ini'), # Common production path 2 - Path('/etc/ovpmon/config.ini'), # Standard linux config path - Path('/opt/ovpn_python_profiler/APP/config.ini') # Path based on traceback - ] - - config = configparser.ConfigParser() - for loc in config_locations: - if loc.exists(): - try: - config.read(loc) - if config.has_section('api') and config.has_option('api', 'secret_key'): - key = config.get('api', 'secret_key') - if key: - print(f"[AUTH] Successfully loaded SECRET_KEY from {loc}") - return key - except Exception as e: - print(f"[AUTH] Error reading config at {loc}: {e}") - continue - - print("[AUTH] WARNING: No config found, using default fallback SECRET_KEY") - return 'ovpmon-secret-change-me' + if key == 'ovpmon-secret-change-me': + print("[AUTH] WARNING: Using default fallback SECRET_KEY") + else: + # Check if it was from env (get_config_value prioritizes env) + import os + if os.getenv('OVPMON_API_SECRET_KEY'): + print("[AUTH] Using SECRET_KEY from OVPMON_API_SECRET_KEY environment variable") + elif os.getenv('OVPMON_SECRET_KEY'): + print("[AUTH] Using SECRET_KEY from OVPMON_SECRET_KEY environment variable") + else: + print("[AUTH] SECRET_KEY loaded (config.ini or fallback)") + + return key SECRET_KEY = get_secret_key() + async def verify_token(authorization: str = Header(None)): if not authorization or not authorization.startswith("Bearer "): print(f"[AUTH] Missing or invalid Authorization header: {authorization[:20] if authorization else 'None'}") diff --git a/APP_PROFILER/utils/config.py b/APP_PROFILER/utils/config.py new file mode 100644 index 0000000..2e08b71 --- /dev/null +++ b/APP_PROFILER/utils/config.py @@ -0,0 +1,32 @@ +import os +import configparser +from pathlib import Path + +# Base directory for the component +BASE_DIR = Path(__file__).resolve().parent.parent +CONFIG_FILE = BASE_DIR / 'config.ini' + +def get_config_value(section: str, key: str, fallback: str = None) -> str: + """ + Get a configuration value with priority: + 1. Environment Variable (OVPMON_{SECTION}_{KEY}) + 2. config.ini in the component root + 3. Fallback value + """ + # 1. Check Environment Variable + env_key = f"OVPMON_{section.upper()}_{key.upper()}".replace('-', '_').replace(' ', '_') + env_val = os.getenv(env_key) + if env_val is not None: + return env_val + + # 2. Check config.ini + if CONFIG_FILE.exists(): + try: + config = configparser.ConfigParser() + config.read(CONFIG_FILE) + if config.has_section(section) and config.has_option(section, key): + return config.get(section, key) + except Exception as e: + print(f"[CONFIG] Error reading {CONFIG_FILE}: {e}") + + return fallback diff --git a/APP_UI/default.conf.template b/APP_UI/default.conf.template index 2a41dc3..196e76d 100644 --- a/APP_UI/default.conf.template +++ b/APP_UI/default.conf.template @@ -9,21 +9,29 @@ server { try_files $uri $uri/ /index.html; } - # Proxy API requests - location /api/v1/ { - proxy_pass http://${OVP_API_HOST}:${OVP_API_PORT}; - } - - location /api/auth { + # Модуль 1: Мониторинг (Flask, порт 5001) + location /api/ { proxy_pass http://${OVP_API_HOST}:${OVP_API_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_header Authorization; } - location /api/ { - proxy_pass http://${OVP_PROFILER_HOST}:${OVP_PROFILER_PORT}; + # Модуль 2: Управление профилями (FastAPI, порт 8000) + # Мы проксируем /profiles-api/ на внутренний /api/ внутри FastAPI + location /profiles-api/ { + proxy_pass http://${OVP_PROFILER_HOST}:${OVP_PROFILER_PORT}/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Для корректной работы OpenAPI/Docs за заголовком + proxy_set_header X-Forwarded-Prefix /profiles-api; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 05454b1..66155bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,7 +70,9 @@ services: networks: - ovp-net environment: - - JWT_SECRET=${JWT_SECRET:-supersecret} + - OVPMON_API_SECRET_KEY=${JWT_SECRET:-supersecret} + - OVPMON_PROFILER_DB_PATH=/app/db/ovpn_profiler.db + networks: ovp-net: