Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Lucas Oliveira
9efd445559 removed sanitizer 2025-12-12 12:58:32 -03:00
Lucas Oliveira
db60ada05f Updated files to use load_dotenv 2025-12-12 10:17:34 -03:00
Lucas Oliveira
d8c7120b5f Pass .env to commands being executed to always match new changes 2025-12-11 17:59:06 -03:00
4 changed files with 80 additions and 59 deletions

View file

@ -2,7 +2,8 @@
import asyncio import asyncio
import json import json
import subprocess import os
import re
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
@ -121,6 +122,36 @@ class ContainerManager:
self._compose_search_log += f"\n 3. Falling back to: {cwd_path.absolute()}" self._compose_search_log += f"\n 3. Falling back to: {cwd_path.absolute()}"
return Path(filename) return Path(filename)
def _get_env_from_file(self) -> Dict[str, str]:
"""Read environment variables from .env file, prioritizing file values over os.environ.
Uses python-dotenv's load_dotenv() for standard .env file parsing, which handles:
- Quoted values (single and double quotes)
- Variable expansion (${VAR})
- Multiline values
- Escaped characters
- Comments
This ensures Docker Compose commands use the latest values from .env file,
even if os.environ has stale values.
"""
from dotenv import load_dotenv
env = dict(os.environ) # Start with current environment
env_file = Path(".env")
if env_file.exists():
try:
# Load .env file with override=True to ensure file values take precedence
# This loads into os.environ, then we copy to our dict
load_dotenv(dotenv_path=env_file, override=True)
# Update our dict with all environment variables (including those from .env)
env.update(os.environ)
except Exception as e:
logger.debug(f"Error reading .env file for Docker Compose: {e}")
return env
def is_available(self) -> bool: def is_available(self) -> bool:
"""Check if container runtime with compose is available.""" """Check if container runtime with compose is available."""
return (self.runtime_info.runtime_type != RuntimeType.NONE and return (self.runtime_info.runtime_type != RuntimeType.NONE and
@ -153,7 +184,6 @@ class ContainerManager:
continue continue
try: try:
import re
content = compose_file.read_text() content = compose_file.read_text()
current_service = None current_service = None
in_ports_section = False in_ports_section = False
@ -245,11 +275,15 @@ class ContainerManager:
cmd.extend(args) cmd.extend(args)
try: try:
# Get environment variables from .env file to ensure latest values
env = self._get_env_from_file()
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
cwd=Path.cwd(), cwd=Path.cwd(),
env=env,
) )
stdout, stderr = await process.communicate() stdout, stderr = await process.communicate()
@ -287,11 +321,15 @@ class ContainerManager:
cmd.extend(args) cmd.extend(args)
try: try:
# Get environment variables from .env file to ensure latest values
env = self._get_env_from_file()
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT, stderr=asyncio.subprocess.STDOUT,
cwd=Path.cwd(), cwd=Path.cwd(),
env=env,
) )
if process.stdout: if process.stdout:
@ -356,11 +394,15 @@ class ContainerManager:
cmd.extend(args) cmd.extend(args)
try: try:
# Get environment variables from .env file to ensure latest values
env = self._get_env_from_file()
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT, stderr=asyncio.subprocess.STDOUT,
cwd=Path.cwd(), cwd=Path.cwd(),
env=env,
) )
except Exception as e: except Exception as e:
success_flag["value"] = False success_flag["value"] = False
@ -926,7 +968,6 @@ class ContainerManager:
async for message, replace_last in self._stream_compose_command(["up", "-d"], up_success, cpu_mode): async for message, replace_last in self._stream_compose_command(["up", "-d"], up_success, cpu_mode):
# Detect error patterns in the output # Detect error patterns in the output
import re
lower_msg = message.lower() lower_msg = message.lower()
# Check for common error patterns # Check for common error patterns
@ -1110,11 +1151,15 @@ class ContainerManager:
cmd.extend(["logs", "-f", service_name]) cmd.extend(["logs", "-f", service_name])
try: try:
# Get environment variables from .env file to ensure latest values
env = self._get_env_from_file()
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT, stderr=asyncio.subprocess.STDOUT,
cwd=Path.cwd(), cwd=Path.cwd(),
env=env,
) )
if process.stdout: if process.stdout:

View file

@ -1,5 +1,6 @@
"""Environment configuration manager for OpenRAG TUI.""" """Environment configuration manager for OpenRAG TUI."""
import os
import secrets import secrets
import string import string
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -7,12 +8,10 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
from dotenv import load_dotenv
from utils.logging_config import get_logger from utils.logging_config import get_logger
logger = get_logger(__name__)
from ..utils.validation import ( from ..utils.validation import (
sanitize_env_value,
validate_documents_paths, validate_documents_paths,
validate_google_oauth_client_id, validate_google_oauth_client_id,
validate_non_empty, validate_non_empty,
@ -20,6 +19,8 @@ from ..utils.validation import (
validate_url, validate_url,
) )
logger = get_logger(__name__)
@dataclass @dataclass
class EnvConfig: class EnvConfig:
@ -119,9 +120,15 @@ class EnvManager:
return f"'{escaped_value}'" return f"'{escaped_value}'"
def load_existing_env(self) -> bool: def load_existing_env(self) -> bool:
"""Load existing .env file if it exists, or fall back to environment variables.""" """Load existing .env file if it exists, or fall back to environment variables.
import os
Uses python-dotenv's load_dotenv() for standard .env file parsing, which handles:
- Quoted values (single and double quotes)
- Variable expansion (${VAR})
- Multiline values
- Escaped characters
- Comments
"""
# Map env vars to config attributes # Map env vars to config attributes
# These are environment variable names, not actual secrets # These are environment variable names, not actual secrets
attr_map = { # pragma: allowlist secret attr_map = { # pragma: allowlist secret
@ -158,36 +165,23 @@ class EnvManager:
loaded_from_file = False loaded_from_file = False
# Try to load from .env file first # Load .env file using python-dotenv for standard parsing
# override=True ensures .env file values take precedence over existing environment variables
if self.env_file.exists(): if self.env_file.exists():
try: try:
with open(self.env_file, "r") as f: # Load .env file with override=True to ensure file values take precedence
for line in f: load_dotenv(dotenv_path=self.env_file, override=True)
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, value = line.split("=", 1)
key = key.strip()
value = sanitize_env_value(value)
if key in attr_map:
setattr(self.config, attr_map[key], value)
loaded_from_file = True loaded_from_file = True
logger.debug(f"Loaded .env file from {self.env_file}")
except Exception as e: except Exception as e:
logger.error("Error loading .env file", error=str(e)) logger.error("Error loading .env file", error=str(e))
# Fall back to environment variables if .env file doesn't exist or failed to load # Map environment variables to config attributes
if not loaded_from_file: # This works whether values came from .env file or existing environment variables
logger.info("No .env file found, loading from environment variables") for env_key, attr_name in attr_map.items():
for env_key, attr_name in attr_map.items(): value = os.environ.get(env_key, "")
value = os.environ.get(env_key, "") if value:
if value: setattr(self.config, attr_name, value)
setattr(self.config, attr_name, value)
return True
return loaded_from_file return loaded_from_file
@ -546,23 +540,19 @@ class EnvManager:
"""Ensure OPENRAG_VERSION is set in .env file to match TUI version.""" """Ensure OPENRAG_VERSION is set in .env file to match TUI version."""
try: try:
from ..utils.version_check import get_current_version from ..utils.version_check import get_current_version
import os
current_version = get_current_version() current_version = get_current_version()
if current_version == "unknown": if current_version == "unknown":
return return
# Check if OPENRAG_VERSION is already set in .env # Check if OPENRAG_VERSION is already set in .env
if self.env_file.exists(): if self.env_file.exists():
env_content = self.env_file.read_text() # Load .env file using load_dotenv
if "OPENRAG_VERSION" in env_content: load_dotenv(dotenv_path=self.env_file, override=False)
# Already set, check if it needs updating existing_value = os.environ.get("OPENRAG_VERSION", "")
for line in env_content.splitlines(): if existing_value and existing_value == current_version:
if line.strip().startswith("OPENRAG_VERSION"): # Already correct, no update needed
existing_value = line.split("=", 1)[1].strip() return
existing_value = sanitize_env_value(existing_value)
if existing_value == current_version:
# Already correct, no update needed
return
break
# Set or update OPENRAG_VERSION # Set or update OPENRAG_VERSION
self.config.openrag_version = current_version self.config.openrag_version = current_version

View file

@ -41,7 +41,8 @@ class WelcomeScreen(Screen):
self.has_env_file = self.env_manager.env_file.exists() self.has_env_file = self.env_manager.env_file.exists()
# Load .env file if it exists # Load .env file if it exists
load_dotenv() # override=True ensures .env file values take precedence over existing environment variables
load_dotenv(override=True)
# Check OAuth config immediately # Check OAuth config immediately
self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool( self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool(

View file

@ -96,21 +96,6 @@ def validate_non_empty(value: str) -> bool:
return bool(value and value.strip()) return bool(value and value.strip())
def sanitize_env_value(value: str) -> str:
"""Sanitize environment variable value."""
# Remove leading/trailing whitespace
value = value.strip()
# Remove quotes if they wrap the entire value
if len(value) >= 2:
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
return value
def validate_documents_paths(paths_str: str) -> tuple[bool, str, list[str]]: def validate_documents_paths(paths_str: str) -> tuple[bool, str, list[str]]:
""" """
Validate comma-separated documents paths for volume mounting. Validate comma-separated documents paths for volume mounting.