""" FTP Server module for FactsDB service Provides secure FTP access to onboarded directories """ import os import threading from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer from typing import Dict, List, Set from .config import Config from .database import DatabaseManager class FTPServerManager: """Manages the FTP server for FactsDB service""" def __init__(self, config): self.config = config # Fix: Remove database initialization from FTP server - it doesn't need database access for basic FTP self.db_manager = None # Don't initialize database for FTP server self.server = None self.is_running = False self.onboarded_directories: Set[str] = set() def start(self): """Start the FTP server""" if self.is_running: print("FTP server is already running") return try: # Create authorizer authorizer = DummyAuthorizer() # Add user with read/write permissions authorizer.add_user( self.config.ftp_server.username, self.config.ftp_server.password, homedir="/app/data", perm="elradf" ) # Create handler handler = FTPHandler handler.authorizer = authorizer # Create server self.server = FTPServer( (self.config.ftp_server.host, self.config.ftp_server.port), handler ) # Set server options self.server.max_cons = 256 self.server.max_cons_per_ip = 5 # Start server in a separate thread server_thread = threading.Thread(target=self._run_server, daemon=True) server_thread.start() self.is_running = True print(f"FTP server started on {self.config.ftp_server.host}:{self.config.ftp_server.port}") except Exception as e: print(f"Failed to start FTP server: {str(e)}") self.is_running = False def _run_server(self): """Run the FTP server""" if self.server: self.server.serve_forever() def stop(self): """Stop the FTP server""" if self.server and self.is_running: self.server.close_all() self.is_running = False print("FTP server stopped") def add_onboarded_directory(self, directory_path: str): """Add an onboarded directory to the FTP server access control""" if os.path.exists(directory_path): self.onboarded_directories.add(os.path.abspath(directory_path)) print(f"Added onboarded directory: {directory_path}") def is_directory_allowed(self, directory_path: str) -> bool: """Check if a directory is allowed (onboarded)""" abs_path = os.path.abspath(directory_path) for onboarded_dir in self.onboarded_directories: if abs_path.startswith(onboarded_dir): return True return False def get_onboarded_directories(self) -> List[str]: """Get list of all onboarded directories""" return list(self.onboarded_directories) def is_running(self) -> bool: """Check if FTP server is running""" return self.is_running # Global FTP server instance _ftp_server_manager = None def get_ftp_server_manager(config: Config) -> FTPServerManager: """Get the global FTP server manager instance""" global _ftp_server_manager if _ftp_server_manager is None: _ftp_server_manager = FTPServerManager(config) return _ftp_server_manager