""" Default Configuration Values for Trixy Application This module provides default configuration values for all deployment modes. It includes factory functions to create default configurations, environment variable mappings, and configuration templates. All defaults are production-ready but can be overridden through configuration files, environment variables, or command line arguments. """ import os import platform import socket from pathlib import Path from typing import Dict, Any, Optional import uuid from .config_models import ( ServerConfig, ClientConfig, StandaloneConfig, NetworkConfig, AudioConfig, MLConfig, PluginConfig, SatelliteConfig, SecurityConfig, LoggingConfig, LogLevel, DeploymentMode ) # Environment variable prefix for Trixy configurations ENV_PREFIX = "TRIXY_" # Default paths relative to application root DEFAULT_PATHS = { "data_directory": "data", "config_directory": "config", "log_directory": "logs", "plugin_directory": "plugins", "model_directory": "models", "asset_directory": "assets", "training_data_directory": "trainer/data", } # Default network ports DEFAULT_PORTS = { "command": 2101, "audio_input": 2102, "audio_output": 2103, "music_output": 2104, "web_interface": 8080, } # Default audio settings based on system capabilities DEFAULT_AUDIO = { "sample_rate": 16000, "channels": 1, "bit_depth": 16, "frame_size": 1024, "max_recording_duration": 60, "silence_threshold": 0.01, "silence_duration": 3.0, "pre_recording_buffer": 1.0, } # Default ML model settings DEFAULT_ML = { "wakeword_threshold": 0.8, "voice_recognition_threshold": 0.7, "model_cache_size": 3, "batch_size": 1, "max_inference_time": 5.0, } def get_system_defaults() -> Dict[str, Any]: """Get system-specific default values.""" hostname = socket.gethostname() system = platform.system().lower() defaults = { "hostname": hostname, "system": system, "satellite_id": str(uuid.uuid4()), "mac_address": get_mac_address(), } # System-specific audio defaults if system == "linux": defaults["audio_backend"] = "alsa" elif system == "windows": defaults["audio_backend"] = "wasapi" elif system == "darwin": # macOS defaults["audio_backend"] = "coreaudio" else: defaults["audio_backend"] = "default" return defaults def get_mac_address() -> str: """Get the system's MAC address.""" try: mac = uuid.getnode() return ':'.join(f'{(mac >> i) & 0xFF:02x}' for i in range(0, 48, 8)) except Exception: return "00:00:00:00:00:00" def get_env_override(key: str, default: Any = None) -> Any: """Get environment variable override with proper type conversion.""" env_key = f"{ENV_PREFIX}{key.upper()}" value = os.getenv(env_key) if value is None: return default # Type conversion based on default value type if isinstance(default, bool): return value.lower() in ('true', '1', 'yes', 'on', 'enabled') elif isinstance(default, int): try: return int(value) except ValueError: return default elif isinstance(default, float): try: return float(value) except ValueError: return default elif isinstance(default, list): # Comma-separated values return [v.strip() for v in value.split(',') if v.strip()] return value def create_default_network_config() -> NetworkConfig: """Create default network configuration.""" return NetworkConfig( command_port=get_env_override("command_port", DEFAULT_PORTS["command"]), command_bind_address=get_env_override("bind_address", "0.0.0.0"), command_max_connections=get_env_override("max_connections", 50), command_timeout=get_env_override("command_timeout", 30), command_buffer_size=get_env_override("command_buffer_size", 8192), raw_audio_input_port=get_env_override("audio_input_port", DEFAULT_PORTS["audio_input"]), raw_audio_output_port=get_env_override("audio_output_port", DEFAULT_PORTS["audio_output"]), raw_music_output_port=get_env_override("music_output_port", DEFAULT_PORTS["music_output"]), audio_bind_address=get_env_override("audio_bind_address", "0.0.0.0"), audio_timeout=get_env_override("audio_timeout", 10), audio_buffer_size=get_env_override("audio_buffer_size", 16384), heartbeat_interval=get_env_override("heartbeat_interval", 30), max_retry_attempts=get_env_override("max_retry_attempts", 5), retry_delay=get_env_override("retry_delay", 5), connection_timeout=get_env_override("connection_timeout", 10), enable_encryption=get_env_override("enable_encryption", False), encryption_key_file=get_env_override("encryption_key_file", None), enable_compression=get_env_override("enable_compression", True), compression_level=get_env_override("compression_level", 6), ) def create_default_audio_config() -> AudioConfig: """Create default audio configuration.""" return AudioConfig( sample_rate=get_env_override("sample_rate", DEFAULT_AUDIO["sample_rate"]), channels=get_env_override("channels", DEFAULT_AUDIO["channels"]), bit_depth=get_env_override("bit_depth", DEFAULT_AUDIO["bit_depth"]), frame_size=get_env_override("frame_size", DEFAULT_AUDIO["frame_size"]), max_recording_duration=get_env_override("max_recording_duration", DEFAULT_AUDIO["max_recording_duration"]), silence_threshold=get_env_override("silence_threshold", DEFAULT_AUDIO["silence_threshold"]), silence_duration=get_env_override("silence_duration", DEFAULT_AUDIO["silence_duration"]), pre_recording_buffer=get_env_override("pre_recording_buffer", DEFAULT_AUDIO["pre_recording_buffer"]), enable_noise_reduction=get_env_override("enable_noise_reduction", True), enable_auto_gain=get_env_override("enable_auto_gain", True), gain_level=get_env_override("gain_level", 1.0), vad_enabled=get_env_override("vad_enabled", True), vad_aggressiveness=get_env_override("vad_aggressiveness", 2), vad_frame_duration=get_env_override("vad_frame_duration", 30), music_sample_rate=get_env_override("music_sample_rate", 48000), music_channels=get_env_override("music_channels", 2), music_bit_depth=get_env_override("music_bit_depth", 16), ) def create_default_ml_config() -> MLConfig: """Create default ML configuration.""" return MLConfig( wakeword_model_path=get_env_override("wakeword_model_path", "models/wakeword/default.pth"), voice_recognition_model_path=get_env_override("voice_recognition_model_path", "models/voice_recognition/default.pth"), wakeword_threshold=get_env_override("wakeword_threshold", DEFAULT_ML["wakeword_threshold"]), voice_recognition_threshold=get_env_override("voice_recognition_threshold", DEFAULT_ML["voice_recognition_threshold"]), model_cache_size=get_env_override("model_cache_size", DEFAULT_ML["model_cache_size"]), enable_gpu=get_env_override("enable_gpu", True), gpu_device_id=get_env_override("gpu_device_id", 0), batch_size=get_env_override("batch_size", DEFAULT_ML["batch_size"]), max_inference_time=get_env_override("max_inference_time", DEFAULT_ML["max_inference_time"]), training_data_path=get_env_override("training_data_path", DEFAULT_PATHS["training_data_directory"]), model_output_path=get_env_override("model_output_path", DEFAULT_PATHS["model_directory"]), training_batch_size=get_env_override("training_batch_size", 32), training_epochs=get_env_override("training_epochs", 100), learning_rate=get_env_override("learning_rate", 0.001), ) def create_default_plugin_config() -> PluginConfig: """Create default plugin configuration.""" return PluginConfig( plugin_directories=get_env_override("plugin_directories", [DEFAULT_PATHS["plugin_directory"]]), auto_load_plugins=get_env_override("auto_load_plugins", True), plugin_timeout=get_env_override("plugin_timeout", 30), max_plugin_memory=get_env_override("max_plugin_memory", 512), enable_plugin_sandboxing=get_env_override("enable_plugin_sandboxing", True), allowed_plugin_apis=set(get_env_override("allowed_plugin_apis", [ "event_handler", "satellite_manager", "asset_manager", "config_manager", "scheduler" ])), enable_hot_reload=get_env_override("enable_hot_reload", True), reload_check_interval=get_env_override("reload_check_interval", 5), max_reload_attempts=get_env_override("max_reload_attempts", 3), ) def create_default_satellite_config() -> SatelliteConfig: """Create default satellite configuration.""" system_defaults = get_system_defaults() return SatelliteConfig( server_host=get_env_override("server_host", "localhost"), server_port=get_env_override("server_port", DEFAULT_PORTS["command"]), auto_reconnect=get_env_override("auto_reconnect", True), reconnect_interval=get_env_override("reconnect_interval", 5), satellite_id=get_env_override("satellite_id", system_defaults["satellite_id"]), room_name=get_env_override("room_name", "default"), alias_name=get_env_override("alias_name", f"trixy-{system_defaults['hostname']}"), mac_address=get_env_override("mac_address", system_defaults["mac_address"]), local_wakeword_processing=get_env_override("local_wakeword_processing", True), local_voice_recognition=get_env_override("local_voice_recognition", False), buffer_audio_locally=get_env_override("buffer_audio_locally", True), stream_quality=get_env_override("stream_quality", "high"), compression_enabled=get_env_override("compression_enabled", True), ) def create_default_security_config() -> SecurityConfig: """Create default security configuration.""" return SecurityConfig( enable_encryption=get_env_override("enable_encryption", False), encryption_algorithm=get_env_override("encryption_algorithm", "AES-256-GCM"), key_rotation_interval=get_env_override("key_rotation_interval", 86400), require_authentication=get_env_override("require_authentication", False), auth_token_expiry=get_env_override("auth_token_expiry", 3600), max_auth_attempts=get_env_override("max_auth_attempts", 3), auth_lockout_duration=get_env_override("auth_lockout_duration", 300), allowed_clients=get_env_override("allowed_clients", []), blocked_clients=get_env_override("blocked_clients", []), max_connections_per_client=get_env_override("max_connections_per_client", 5), enable_audit_logging=get_env_override("enable_audit_logging", True), audit_log_file=get_env_override("audit_log_file", "logs/security_audit.log"), enable_intrusion_detection=get_env_override("enable_intrusion_detection", False), ) def create_default_logging_config(debug_mode: bool = False) -> LoggingConfig: """Create default logging configuration.""" log_level = LogLevel.DEBUG if debug_mode else LogLevel.INFO return LoggingConfig( log_level=LogLevel(get_env_override("log_level", log_level.value)), log_format=get_env_override("log_format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"), date_format=get_env_override("date_format", "%Y-%m-%d %H:%M:%S"), enable_file_logging=get_env_override("enable_file_logging", True), log_file_path=get_env_override("log_file_path", "logs/trixy.log"), max_log_file_size=get_env_override("max_log_file_size", 10485760), # 10MB log_file_backup_count=get_env_override("log_file_backup_count", 5), enable_console_logging=get_env_override("enable_console_logging", True), console_log_level=LogLevel(get_env_override("console_log_level", log_level.value)), enable_event_logging=get_env_override("enable_event_logging", True), event_log_file=get_env_override("event_log_file", "logs/events.log"), enable_performance_logging=get_env_override("enable_performance_logging", debug_mode), performance_log_file=get_env_override("performance_log_file", "logs/performance.log"), debug_mode=get_env_override("debug_mode", debug_mode), verbose_errors=get_env_override("verbose_errors", debug_mode), log_stack_traces=get_env_override("log_stack_traces", debug_mode), ) def create_default_server_config(debug_mode: bool = False) -> ServerConfig: """Create default server configuration.""" return ServerConfig( mode=DeploymentMode.SERVER, debug_mode=debug_mode, data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]), config_file=get_env_override("config_file", "config/server_config.json"), network=create_default_network_config(), audio=create_default_audio_config(), ml=create_default_ml_config(), plugins=create_default_plugin_config(), security=create_default_security_config(), logging=create_default_logging_config(debug_mode), registration_mode_timeout=get_env_override("registration_mode_timeout", 60), max_satellites=get_env_override("max_satellites", 50), satellite_heartbeat_timeout=get_env_override("satellite_heartbeat_timeout", 60), enable_web_interface=get_env_override("enable_web_interface", False), web_interface_port=get_env_override("web_interface_port", DEFAULT_PORTS["web_interface"]), asset_profile=get_env_override("asset_profile", "default"), asset_cache_size=get_env_override("asset_cache_size", 100), conversation_timeout=get_env_override("conversation_timeout", 300), max_concurrent_conversations=get_env_override("max_concurrent_conversations", 10), ) def create_default_client_config(debug_mode: bool = False) -> ClientConfig: """Create default client configuration.""" return ClientConfig( mode=DeploymentMode.CLIENT, debug_mode=debug_mode, data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]), config_file=get_env_override("config_file", "config/client_config.json"), satellite=create_default_satellite_config(), audio=create_default_audio_config(), ml=create_default_ml_config(), logging=create_default_logging_config(debug_mode), enable_local_processing=get_env_override("enable_local_processing", True), local_model_cache_size=get_env_override("local_model_cache_size", 50), audio_buffer_size=get_env_override("audio_buffer_size", 8192), status_report_interval=get_env_override("status_report_interval", 30), health_check_interval=get_env_override("health_check_interval", 10), ) def create_default_standalone_config(debug_mode: bool = False) -> StandaloneConfig: """Create default standalone configuration.""" return StandaloneConfig( mode=DeploymentMode.STANDALONE, debug_mode=debug_mode, data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]), config_file=get_env_override("config_file", "config/standalone_config.json"), network=create_default_network_config(), audio=create_default_audio_config(), ml=create_default_ml_config(), plugins=create_default_plugin_config(), security=create_default_security_config(), logging=create_default_logging_config(debug_mode), enable_server_sync=get_env_override("enable_server_sync", False), sync_server_host=get_env_override("sync_server_host", None), sync_server_port=get_env_override("sync_server_port", None), sync_interval=get_env_override("sync_interval", 300), registration_mode_timeout=get_env_override("registration_mode_timeout", 60), max_satellites=get_env_override("max_satellites", 10), # Lower for standalone satellite_heartbeat_timeout=get_env_override("satellite_heartbeat_timeout", 60), enable_web_interface=get_env_override("enable_web_interface", True), web_interface_port=get_env_override("web_interface_port", DEFAULT_PORTS["web_interface"]), asset_profile=get_env_override("asset_profile", "default"), asset_cache_size=get_env_override("asset_cache_size", 50), # Lower for standalone conversation_timeout=get_env_override("conversation_timeout", 300), max_concurrent_conversations=get_env_override("max_concurrent_conversations", 5), # Lower for standalone ) # Factory function mapping DEFAULT_CONFIG_FACTORIES = { DeploymentMode.SERVER: create_default_server_config, DeploymentMode.CLIENT: create_default_client_config, DeploymentMode.STANDALONE: create_default_standalone_config, } def create_default_config_for_mode(mode: DeploymentMode, debug_mode: bool = False): """Create default configuration for the specified mode.""" factory = DEFAULT_CONFIG_FACTORIES[mode] return factory(debug_mode=debug_mode) def get_environment_overrides() -> Dict[str, str]: """Get all Trixy environment variables.""" return { key[len(ENV_PREFIX):].lower(): value for key, value in os.environ.items() if key.startswith(ENV_PREFIX) } def apply_environment_overrides(config_dict: Dict[str, Any]) -> Dict[str, Any]: """Apply environment variable overrides to configuration dictionary.""" overrides = get_environment_overrides() # Simple flat override for now - can be enhanced for nested overrides for key, value in overrides.items(): if key in config_dict: # Try to maintain type consistency original_value = config_dict[key] if isinstance(original_value, bool): config_dict[key] = value.lower() in ('true', '1', 'yes', 'on', 'enabled') elif isinstance(original_value, int): try: config_dict[key] = int(value) except ValueError: pass # Keep original value elif isinstance(original_value, float): try: config_dict[key] = float(value) except ValueError: pass # Keep original value else: config_dict[key] = value return config_dict def get_config_template(mode: DeploymentMode) -> Dict[str, Any]: """Get configuration template for documentation/generation purposes.""" default_config = create_default_config_for_mode(mode, debug_mode=False) return default_config.to_dict() def validate_environment() -> Dict[str, str]: """Validate environment and return any warnings/errors.""" issues = {} # Check for required directories required_dirs = [ DEFAULT_PATHS["data_directory"], DEFAULT_PATHS["log_directory"], DEFAULT_PATHS["config_directory"], ] for dir_path in required_dirs: if not Path(dir_path).exists(): issues[f"missing_directory_{dir_path}"] = f"Directory {dir_path} does not exist and may need to be created" # Check environment variable conflicts env_overrides = get_environment_overrides() conflicting_vars = [] # Check for potentially conflicting port assignments port_vars = [k for k in env_overrides.keys() if 'port' in k.lower()] if len(port_vars) > 1: issues["port_conflicts"] = f"Multiple port environment variables detected: {port_vars}. Ensure no conflicts." return issues # Environment variable documentation ENV_VAR_DOCS = { "TRIXY_DEBUG_MODE": "Enable debug mode (true/false)", "TRIXY_LOG_LEVEL": "Logging level (debug/info/warning/error/critical)", "TRIXY_COMMAND_PORT": "Command socket port (default: 2101)", "TRIXY_AUDIO_INPUT_PORT": "Audio input stream port (default: 2102)", "TRIXY_AUDIO_OUTPUT_PORT": "Audio output stream port (default: 2103)", "TRIXY_MUSIC_OUTPUT_PORT": "Music output stream port (default: 2104)", "TRIXY_WEB_INTERFACE_PORT": "Web interface port (default: 8080)", "TRIXY_SERVER_HOST": "Server host for client mode (default: localhost)", "TRIXY_ENABLE_ENCRYPTION": "Enable network encryption (true/false)", "TRIXY_ENABLE_GPU": "Enable GPU acceleration for ML models (true/false)", "TRIXY_MAX_SATELLITES": "Maximum number of connected satellites", "TRIXY_PLUGIN_DIRECTORIES": "Comma-separated list of plugin directories", } def get_env_var_documentation() -> Dict[str, str]: """Get documentation for all supported environment variables.""" return ENV_VAR_DOCS.copy()