#!/usr/bin/env python3 import re import argparse import atexit # Import atexit import shutil # Import shutil for directory removal import logging # Import the logging module import sys # Import sys for stdout import os # Import os module from camoufox.server import launch_server def parse_proxy_url(url): """Parse proxy URL in format proto://user:pass@host:port""" pattern = r'([^:]+)://(?:([^:]+):([^@]+)@)?([^:]+):(\d+)' match = re.match(pattern, url) if not match: raise ValueError('Invalid proxy URL format. Expected proto://[user:pass@]host:port') proto, username, password, host, port = match.groups() # Ensure username and password are strings, not None proxy_config = { 'server': f'{proto}://{host}:{port}', 'username': username or '', 'password': password or '' } # Remove empty credentials if not proxy_config['username']: del proxy_config['username'] if not proxy_config['password']: del proxy_config['password'] return proxy_config def main(): parser = argparse.ArgumentParser(description='Launch Camoufox server with optional proxy support') parser.add_argument('--proxy-url', help='Optional proxy URL in format proto://user:pass@host:port (supports http, https, socks5)') parser.add_argument('--ws-host', default='localhost', help='WebSocket server host address (e.g., localhost, 0.0.0.0)') parser.add_argument('--port', type=int, default=0, help='WebSocket server port (0 for random)') parser.add_argument('--ws-path', default='camoufox', help='WebSocket server path') parser.add_argument('--headless', action='store_true', help='Run browser in headless mode') parser.add_argument('--geoip', nargs='?', const=True, default=False, help='Enable geo IP protection. Can specify IP address or use True for automatic detection') parser.add_argument('--locale', help='Locale(s) to use (e.g. "en-US" or "en-US,fr-FR")') parser.add_argument('--block-images', action='store_true', help='Block image requests to save bandwidth') parser.add_argument('--block-webrtc', action='store_true', help='Block WebRTC entirely') parser.add_argument('--humanize', nargs='?', const=True, type=float, help='Humanize cursor movements. Can specify max duration in seconds') parser.add_argument('--extensions', type=str, help='Comma-separated list of extension paths to enable (XPI files or extracted directories). Use quotes if paths contain spaces.') args = parser.parse_args() proxy_config = None if args.proxy_url: try: proxy_config = parse_proxy_url(args.proxy_url) print(f"Using proxy configuration: {args.proxy_url}") except ValueError as e: print(f'Error parsing proxy URL: {e}') return else: print("No proxy URL provided. Running without proxy.") # --- Basic Logging Configuration --- # Configure the root logger to show INFO level messages # This might capture logs from camoufox or its dependencies (like websockets) log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') log_handler = logging.StreamHandler(sys.stdout) # Log to standard output log_handler.setFormatter(log_formatter) root_logger = logging.getLogger() # Remove existing handlers to avoid duplicates if script is re-run in same process for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) root_logger.addHandler(log_handler) # Set level to DEBUG for more detailed output from Camoufox/Playwright root_logger.setLevel(logging.DEBUG) logging.debug("DEBUG logging enabled. Starting Camoufox server setup...") # --- End Logging Configuration --- try: # --- Check DISPLAY environment variable --- display_var = os.environ.get('DISPLAY') logging.info(f"Value of DISPLAY environment variable: {display_var}") # --- End Check --- # Build config dictionary config = { 'headless': args.headless, 'geoip': args.geoip, # 'proxy': proxy_config, # Add proxy config only if it exists 'host': args.ws_host, # Add the host argument 'port': args.port, 'ws_path': args.ws_path, # Explicitly pass DISPLAY environment variable to Playwright 'env': {'DISPLAY': os.environ.get('DISPLAY')} } # Add proxy to config only if it was successfully parsed if proxy_config: config['proxy'] = proxy_config # Add optional parameters if args.locale: config['locale'] = args.locale if args.block_images: config['block_images'] = True if args.block_webrtc: config['block_webrtc'] = True if args.humanize: config['humanize'] = args.humanize if isinstance(args.humanize, float) else True # Exclude default addons including uBlock Origin config['exclude_addons'] = ['ublock_origin', 'default_addons'] print('Excluded default addons including uBlock Origin') # Add custom extensions if specified if args.extensions: from pathlib import Path valid_extensions = [] # Split comma-separated extensions extensions_list = [ext.strip() for ext in args.extensions.split(',')] temp_dirs_to_cleanup = [] # List to store temp dirs # Register cleanup function def cleanup_temp_dirs(): for temp_dir in temp_dirs_to_cleanup: try: shutil.rmtree(temp_dir) print(f"Cleaned up temporary extension directory: {temp_dir}") except Exception as e: print(f"Warning: Failed to clean up temp dir {temp_dir}: {e}") atexit.register(cleanup_temp_dirs) for ext_path in extensions_list: # Convert to absolute path ext_path = Path(ext_path).absolute() if not ext_path.exists(): print(f"Warning: Extension path does not exist: {ext_path}") continue if ext_path.is_file() and ext_path.suffix == '.xpi': # Extract XPI to temporary directory import tempfile import zipfile try: temp_dir = tempfile.mkdtemp(prefix=f"camoufox_ext_{ext_path.stem}_") temp_dirs_to_cleanup.append(temp_dir) # Add to cleanup list with zipfile.ZipFile(ext_path, 'r') as zip_ref: zip_ref.extractall(temp_dir) valid_extensions.append(temp_dir) print(f"Successfully loaded extension: {ext_path.name} (extracted to {temp_dir})") except Exception as e: print(f"Error loading extension {ext_path}: {str(e)}") # Remove from cleanup list if extraction failed before adding to valid_extensions if temp_dir in temp_dirs_to_cleanup: temp_dirs_to_cleanup.remove(temp_dir) continue elif ext_path.is_dir(): # Check if it's a valid Firefox extension if (ext_path / 'manifest.json').exists(): valid_extensions.append(str(ext_path)) print(f"Successfully loaded extension: {ext_path.name}") else: print(f"Warning: Directory is not a valid Firefox extension: {ext_path}") else: print(f"Warning: Invalid extension path: {ext_path}") if valid_extensions: config['addons'] = valid_extensions print(f"Loaded {len(valid_extensions)} extensions") else: print("Warning: No valid extensions were loaded") server = launch_server(**config) except Exception as e: print(f'Error launching server: {str(e)}') if 'Browser.setBrowserProxy' in str(e): print('Note: The browser may not support SOCKS5 proxy authentication') return print(f'\nCamoufox server started successfully!') print(f'WebSocket endpoint: {server.ws_endpoint}\n') if __name__ == '__main__': main()