config_defaults.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. """
  2. Default Configuration Values for Trixy Application
  3. This module provides default configuration values for all deployment modes.
  4. It includes factory functions to create default configurations, environment
  5. variable mappings, and configuration templates.
  6. All defaults are production-ready but can be overridden through configuration
  7. files, environment variables, or command line arguments.
  8. """
  9. import os
  10. import platform
  11. import socket
  12. from pathlib import Path
  13. from typing import Dict, Any, Optional
  14. import uuid
  15. from .config_models import (
  16. ServerConfig, ClientConfig, StandaloneConfig,
  17. NetworkConfig, AudioConfig, MLConfig, PluginConfig,
  18. SatelliteConfig, SecurityConfig, LoggingConfig,
  19. LogLevel, DeploymentMode
  20. )
  21. # Environment variable prefix for Trixy configurations
  22. ENV_PREFIX = "TRIXY_"
  23. # Default paths relative to application root
  24. DEFAULT_PATHS = {
  25. "data_directory": "data",
  26. "config_directory": "config",
  27. "log_directory": "logs",
  28. "plugin_directory": "plugins",
  29. "model_directory": "models",
  30. "asset_directory": "assets",
  31. "training_data_directory": "trainer/data",
  32. }
  33. # Default network ports
  34. DEFAULT_PORTS = {
  35. "command": 2101,
  36. "audio_input": 2102,
  37. "audio_output": 2103,
  38. "music_output": 2104,
  39. "web_interface": 8080,
  40. }
  41. # Default audio settings based on system capabilities
  42. DEFAULT_AUDIO = {
  43. "sample_rate": 16000,
  44. "channels": 1,
  45. "bit_depth": 16,
  46. "frame_size": 1024,
  47. "max_recording_duration": 60,
  48. "silence_threshold": 0.01,
  49. "silence_duration": 3.0,
  50. "pre_recording_buffer": 1.0,
  51. }
  52. # Default ML model settings
  53. DEFAULT_ML = {
  54. "wakeword_threshold": 0.8,
  55. "voice_recognition_threshold": 0.7,
  56. "model_cache_size": 3,
  57. "batch_size": 1,
  58. "max_inference_time": 5.0,
  59. }
  60. def get_system_defaults() -> Dict[str, Any]:
  61. """Get system-specific default values."""
  62. hostname = socket.gethostname()
  63. system = platform.system().lower()
  64. defaults = {
  65. "hostname": hostname,
  66. "system": system,
  67. "satellite_id": str(uuid.uuid4()),
  68. "mac_address": get_mac_address(),
  69. }
  70. # System-specific audio defaults
  71. if system == "linux":
  72. defaults["audio_backend"] = "alsa"
  73. elif system == "windows":
  74. defaults["audio_backend"] = "wasapi"
  75. elif system == "darwin": # macOS
  76. defaults["audio_backend"] = "coreaudio"
  77. else:
  78. defaults["audio_backend"] = "default"
  79. return defaults
  80. def get_mac_address() -> str:
  81. """Get the system's MAC address."""
  82. try:
  83. mac = uuid.getnode()
  84. return ':'.join(f'{(mac >> i) & 0xFF:02x}' for i in range(0, 48, 8))
  85. except Exception:
  86. return "00:00:00:00:00:00"
  87. def get_env_override(key: str, default: Any = None) -> Any:
  88. """Get environment variable override with proper type conversion."""
  89. env_key = f"{ENV_PREFIX}{key.upper()}"
  90. value = os.getenv(env_key)
  91. if value is None:
  92. return default
  93. # Type conversion based on default value type
  94. if isinstance(default, bool):
  95. return value.lower() in ('true', '1', 'yes', 'on', 'enabled')
  96. elif isinstance(default, int):
  97. try:
  98. return int(value)
  99. except ValueError:
  100. return default
  101. elif isinstance(default, float):
  102. try:
  103. return float(value)
  104. except ValueError:
  105. return default
  106. elif isinstance(default, list):
  107. # Comma-separated values
  108. return [v.strip() for v in value.split(',') if v.strip()]
  109. return value
  110. def create_default_network_config() -> NetworkConfig:
  111. """Create default network configuration."""
  112. return NetworkConfig(
  113. command_port=get_env_override("command_port", DEFAULT_PORTS["command"]),
  114. command_bind_address=get_env_override("bind_address", "0.0.0.0"),
  115. command_max_connections=get_env_override("max_connections", 50),
  116. command_timeout=get_env_override("command_timeout", 30),
  117. command_buffer_size=get_env_override("command_buffer_size", 8192),
  118. raw_audio_input_port=get_env_override("audio_input_port", DEFAULT_PORTS["audio_input"]),
  119. raw_audio_output_port=get_env_override("audio_output_port", DEFAULT_PORTS["audio_output"]),
  120. raw_music_output_port=get_env_override("music_output_port", DEFAULT_PORTS["music_output"]),
  121. audio_bind_address=get_env_override("audio_bind_address", "0.0.0.0"),
  122. audio_timeout=get_env_override("audio_timeout", 10),
  123. audio_buffer_size=get_env_override("audio_buffer_size", 16384),
  124. heartbeat_interval=get_env_override("heartbeat_interval", 30),
  125. max_retry_attempts=get_env_override("max_retry_attempts", 5),
  126. retry_delay=get_env_override("retry_delay", 5),
  127. connection_timeout=get_env_override("connection_timeout", 10),
  128. enable_encryption=get_env_override("enable_encryption", False),
  129. encryption_key_file=get_env_override("encryption_key_file", None),
  130. enable_compression=get_env_override("enable_compression", True),
  131. compression_level=get_env_override("compression_level", 6),
  132. )
  133. def create_default_audio_config() -> AudioConfig:
  134. """Create default audio configuration."""
  135. return AudioConfig(
  136. sample_rate=get_env_override("sample_rate", DEFAULT_AUDIO["sample_rate"]),
  137. channels=get_env_override("channels", DEFAULT_AUDIO["channels"]),
  138. bit_depth=get_env_override("bit_depth", DEFAULT_AUDIO["bit_depth"]),
  139. frame_size=get_env_override("frame_size", DEFAULT_AUDIO["frame_size"]),
  140. max_recording_duration=get_env_override("max_recording_duration", DEFAULT_AUDIO["max_recording_duration"]),
  141. silence_threshold=get_env_override("silence_threshold", DEFAULT_AUDIO["silence_threshold"]),
  142. silence_duration=get_env_override("silence_duration", DEFAULT_AUDIO["silence_duration"]),
  143. pre_recording_buffer=get_env_override("pre_recording_buffer", DEFAULT_AUDIO["pre_recording_buffer"]),
  144. enable_noise_reduction=get_env_override("enable_noise_reduction", True),
  145. enable_auto_gain=get_env_override("enable_auto_gain", True),
  146. gain_level=get_env_override("gain_level", 1.0),
  147. vad_enabled=get_env_override("vad_enabled", True),
  148. vad_aggressiveness=get_env_override("vad_aggressiveness", 2),
  149. vad_frame_duration=get_env_override("vad_frame_duration", 30),
  150. music_sample_rate=get_env_override("music_sample_rate", 48000),
  151. music_channels=get_env_override("music_channels", 2),
  152. music_bit_depth=get_env_override("music_bit_depth", 16),
  153. )
  154. def create_default_ml_config() -> MLConfig:
  155. """Create default ML configuration."""
  156. return MLConfig(
  157. wakeword_model_path=get_env_override("wakeword_model_path", "models/wakeword/default.pth"),
  158. voice_recognition_model_path=get_env_override("voice_recognition_model_path", "models/voice_recognition/default.pth"),
  159. wakeword_threshold=get_env_override("wakeword_threshold", DEFAULT_ML["wakeword_threshold"]),
  160. voice_recognition_threshold=get_env_override("voice_recognition_threshold", DEFAULT_ML["voice_recognition_threshold"]),
  161. model_cache_size=get_env_override("model_cache_size", DEFAULT_ML["model_cache_size"]),
  162. enable_gpu=get_env_override("enable_gpu", True),
  163. gpu_device_id=get_env_override("gpu_device_id", 0),
  164. batch_size=get_env_override("batch_size", DEFAULT_ML["batch_size"]),
  165. max_inference_time=get_env_override("max_inference_time", DEFAULT_ML["max_inference_time"]),
  166. training_data_path=get_env_override("training_data_path", DEFAULT_PATHS["training_data_directory"]),
  167. model_output_path=get_env_override("model_output_path", DEFAULT_PATHS["model_directory"]),
  168. training_batch_size=get_env_override("training_batch_size", 32),
  169. training_epochs=get_env_override("training_epochs", 100),
  170. learning_rate=get_env_override("learning_rate", 0.001),
  171. )
  172. def create_default_plugin_config() -> PluginConfig:
  173. """Create default plugin configuration."""
  174. return PluginConfig(
  175. plugin_directories=get_env_override("plugin_directories", [DEFAULT_PATHS["plugin_directory"]]),
  176. auto_load_plugins=get_env_override("auto_load_plugins", True),
  177. plugin_timeout=get_env_override("plugin_timeout", 30),
  178. max_plugin_memory=get_env_override("max_plugin_memory", 512),
  179. enable_plugin_sandboxing=get_env_override("enable_plugin_sandboxing", True),
  180. allowed_plugin_apis=set(get_env_override("allowed_plugin_apis", [
  181. "event_handler", "satellite_manager", "asset_manager",
  182. "config_manager", "scheduler"
  183. ])),
  184. enable_hot_reload=get_env_override("enable_hot_reload", True),
  185. reload_check_interval=get_env_override("reload_check_interval", 5),
  186. max_reload_attempts=get_env_override("max_reload_attempts", 3),
  187. )
  188. def create_default_satellite_config() -> SatelliteConfig:
  189. """Create default satellite configuration."""
  190. system_defaults = get_system_defaults()
  191. return SatelliteConfig(
  192. server_host=get_env_override("server_host", "localhost"),
  193. server_port=get_env_override("server_port", DEFAULT_PORTS["command"]),
  194. auto_reconnect=get_env_override("auto_reconnect", True),
  195. reconnect_interval=get_env_override("reconnect_interval", 5),
  196. satellite_id=get_env_override("satellite_id", system_defaults["satellite_id"]),
  197. room_name=get_env_override("room_name", "default"),
  198. alias_name=get_env_override("alias_name", f"trixy-{system_defaults['hostname']}"),
  199. mac_address=get_env_override("mac_address", system_defaults["mac_address"]),
  200. local_wakeword_processing=get_env_override("local_wakeword_processing", True),
  201. local_voice_recognition=get_env_override("local_voice_recognition", False),
  202. buffer_audio_locally=get_env_override("buffer_audio_locally", True),
  203. stream_quality=get_env_override("stream_quality", "high"),
  204. compression_enabled=get_env_override("compression_enabled", True),
  205. )
  206. def create_default_security_config() -> SecurityConfig:
  207. """Create default security configuration."""
  208. return SecurityConfig(
  209. enable_encryption=get_env_override("enable_encryption", False),
  210. encryption_algorithm=get_env_override("encryption_algorithm", "AES-256-GCM"),
  211. key_rotation_interval=get_env_override("key_rotation_interval", 86400),
  212. require_authentication=get_env_override("require_authentication", False),
  213. auth_token_expiry=get_env_override("auth_token_expiry", 3600),
  214. max_auth_attempts=get_env_override("max_auth_attempts", 3),
  215. auth_lockout_duration=get_env_override("auth_lockout_duration", 300),
  216. allowed_clients=get_env_override("allowed_clients", []),
  217. blocked_clients=get_env_override("blocked_clients", []),
  218. max_connections_per_client=get_env_override("max_connections_per_client", 5),
  219. enable_audit_logging=get_env_override("enable_audit_logging", True),
  220. audit_log_file=get_env_override("audit_log_file", "logs/security_audit.log"),
  221. enable_intrusion_detection=get_env_override("enable_intrusion_detection", False),
  222. )
  223. def create_default_logging_config(debug_mode: bool = False) -> LoggingConfig:
  224. """Create default logging configuration."""
  225. log_level = LogLevel.DEBUG if debug_mode else LogLevel.INFO
  226. return LoggingConfig(
  227. log_level=LogLevel(get_env_override("log_level", log_level.value)),
  228. log_format=get_env_override("log_format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
  229. date_format=get_env_override("date_format", "%Y-%m-%d %H:%M:%S"),
  230. enable_file_logging=get_env_override("enable_file_logging", True),
  231. log_file_path=get_env_override("log_file_path", "logs/trixy.log"),
  232. max_log_file_size=get_env_override("max_log_file_size", 10485760), # 10MB
  233. log_file_backup_count=get_env_override("log_file_backup_count", 5),
  234. enable_console_logging=get_env_override("enable_console_logging", True),
  235. console_log_level=LogLevel(get_env_override("console_log_level", log_level.value)),
  236. enable_event_logging=get_env_override("enable_event_logging", True),
  237. event_log_file=get_env_override("event_log_file", "logs/events.log"),
  238. enable_performance_logging=get_env_override("enable_performance_logging", debug_mode),
  239. performance_log_file=get_env_override("performance_log_file", "logs/performance.log"),
  240. debug_mode=get_env_override("debug_mode", debug_mode),
  241. verbose_errors=get_env_override("verbose_errors", debug_mode),
  242. log_stack_traces=get_env_override("log_stack_traces", debug_mode),
  243. )
  244. def create_default_server_config(debug_mode: bool = False) -> ServerConfig:
  245. """Create default server configuration."""
  246. return ServerConfig(
  247. mode=DeploymentMode.SERVER,
  248. debug_mode=debug_mode,
  249. data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]),
  250. config_file=get_env_override("config_file", "config/server_config.json"),
  251. network=create_default_network_config(),
  252. audio=create_default_audio_config(),
  253. ml=create_default_ml_config(),
  254. plugins=create_default_plugin_config(),
  255. security=create_default_security_config(),
  256. logging=create_default_logging_config(debug_mode),
  257. registration_mode_timeout=get_env_override("registration_mode_timeout", 60),
  258. max_satellites=get_env_override("max_satellites", 50),
  259. satellite_heartbeat_timeout=get_env_override("satellite_heartbeat_timeout", 60),
  260. enable_web_interface=get_env_override("enable_web_interface", False),
  261. web_interface_port=get_env_override("web_interface_port", DEFAULT_PORTS["web_interface"]),
  262. asset_profile=get_env_override("asset_profile", "default"),
  263. asset_cache_size=get_env_override("asset_cache_size", 100),
  264. conversation_timeout=get_env_override("conversation_timeout", 300),
  265. max_concurrent_conversations=get_env_override("max_concurrent_conversations", 10),
  266. )
  267. def create_default_client_config(debug_mode: bool = False) -> ClientConfig:
  268. """Create default client configuration."""
  269. return ClientConfig(
  270. mode=DeploymentMode.CLIENT,
  271. debug_mode=debug_mode,
  272. data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]),
  273. config_file=get_env_override("config_file", "config/client_config.json"),
  274. satellite=create_default_satellite_config(),
  275. audio=create_default_audio_config(),
  276. ml=create_default_ml_config(),
  277. logging=create_default_logging_config(debug_mode),
  278. enable_local_processing=get_env_override("enable_local_processing", True),
  279. local_model_cache_size=get_env_override("local_model_cache_size", 50),
  280. audio_buffer_size=get_env_override("audio_buffer_size", 8192),
  281. status_report_interval=get_env_override("status_report_interval", 30),
  282. health_check_interval=get_env_override("health_check_interval", 10),
  283. )
  284. def create_default_standalone_config(debug_mode: bool = False) -> StandaloneConfig:
  285. """Create default standalone configuration."""
  286. return StandaloneConfig(
  287. mode=DeploymentMode.STANDALONE,
  288. debug_mode=debug_mode,
  289. data_directory=get_env_override("data_directory", DEFAULT_PATHS["data_directory"]),
  290. config_file=get_env_override("config_file", "config/standalone_config.json"),
  291. network=create_default_network_config(),
  292. audio=create_default_audio_config(),
  293. ml=create_default_ml_config(),
  294. plugins=create_default_plugin_config(),
  295. security=create_default_security_config(),
  296. logging=create_default_logging_config(debug_mode),
  297. enable_server_sync=get_env_override("enable_server_sync", False),
  298. sync_server_host=get_env_override("sync_server_host", None),
  299. sync_server_port=get_env_override("sync_server_port", None),
  300. sync_interval=get_env_override("sync_interval", 300),
  301. registration_mode_timeout=get_env_override("registration_mode_timeout", 60),
  302. max_satellites=get_env_override("max_satellites", 10), # Lower for standalone
  303. satellite_heartbeat_timeout=get_env_override("satellite_heartbeat_timeout", 60),
  304. enable_web_interface=get_env_override("enable_web_interface", True),
  305. web_interface_port=get_env_override("web_interface_port", DEFAULT_PORTS["web_interface"]),
  306. asset_profile=get_env_override("asset_profile", "default"),
  307. asset_cache_size=get_env_override("asset_cache_size", 50), # Lower for standalone
  308. conversation_timeout=get_env_override("conversation_timeout", 300),
  309. max_concurrent_conversations=get_env_override("max_concurrent_conversations", 5), # Lower for standalone
  310. )
  311. # Factory function mapping
  312. DEFAULT_CONFIG_FACTORIES = {
  313. DeploymentMode.SERVER: create_default_server_config,
  314. DeploymentMode.CLIENT: create_default_client_config,
  315. DeploymentMode.STANDALONE: create_default_standalone_config,
  316. }
  317. def create_default_config_for_mode(mode: DeploymentMode, debug_mode: bool = False):
  318. """Create default configuration for the specified mode."""
  319. factory = DEFAULT_CONFIG_FACTORIES[mode]
  320. return factory(debug_mode=debug_mode)
  321. def get_environment_overrides() -> Dict[str, str]:
  322. """Get all Trixy environment variables."""
  323. return {
  324. key[len(ENV_PREFIX):].lower(): value
  325. for key, value in os.environ.items()
  326. if key.startswith(ENV_PREFIX)
  327. }
  328. def apply_environment_overrides(config_dict: Dict[str, Any]) -> Dict[str, Any]:
  329. """Apply environment variable overrides to configuration dictionary."""
  330. overrides = get_environment_overrides()
  331. # Simple flat override for now - can be enhanced for nested overrides
  332. for key, value in overrides.items():
  333. if key in config_dict:
  334. # Try to maintain type consistency
  335. original_value = config_dict[key]
  336. if isinstance(original_value, bool):
  337. config_dict[key] = value.lower() in ('true', '1', 'yes', 'on', 'enabled')
  338. elif isinstance(original_value, int):
  339. try:
  340. config_dict[key] = int(value)
  341. except ValueError:
  342. pass # Keep original value
  343. elif isinstance(original_value, float):
  344. try:
  345. config_dict[key] = float(value)
  346. except ValueError:
  347. pass # Keep original value
  348. else:
  349. config_dict[key] = value
  350. return config_dict
  351. def get_config_template(mode: DeploymentMode) -> Dict[str, Any]:
  352. """Get configuration template for documentation/generation purposes."""
  353. default_config = create_default_config_for_mode(mode, debug_mode=False)
  354. return default_config.to_dict()
  355. def validate_environment() -> Dict[str, str]:
  356. """Validate environment and return any warnings/errors."""
  357. issues = {}
  358. # Check for required directories
  359. required_dirs = [
  360. DEFAULT_PATHS["data_directory"],
  361. DEFAULT_PATHS["log_directory"],
  362. DEFAULT_PATHS["config_directory"],
  363. ]
  364. for dir_path in required_dirs:
  365. if not Path(dir_path).exists():
  366. issues[f"missing_directory_{dir_path}"] = f"Directory {dir_path} does not exist and may need to be created"
  367. # Check environment variable conflicts
  368. env_overrides = get_environment_overrides()
  369. conflicting_vars = []
  370. # Check for potentially conflicting port assignments
  371. port_vars = [k for k in env_overrides.keys() if 'port' in k.lower()]
  372. if len(port_vars) > 1:
  373. issues["port_conflicts"] = f"Multiple port environment variables detected: {port_vars}. Ensure no conflicts."
  374. return issues
  375. # Environment variable documentation
  376. ENV_VAR_DOCS = {
  377. "TRIXY_DEBUG_MODE": "Enable debug mode (true/false)",
  378. "TRIXY_LOG_LEVEL": "Logging level (debug/info/warning/error/critical)",
  379. "TRIXY_COMMAND_PORT": "Command socket port (default: 2101)",
  380. "TRIXY_AUDIO_INPUT_PORT": "Audio input stream port (default: 2102)",
  381. "TRIXY_AUDIO_OUTPUT_PORT": "Audio output stream port (default: 2103)",
  382. "TRIXY_MUSIC_OUTPUT_PORT": "Music output stream port (default: 2104)",
  383. "TRIXY_WEB_INTERFACE_PORT": "Web interface port (default: 8080)",
  384. "TRIXY_SERVER_HOST": "Server host for client mode (default: localhost)",
  385. "TRIXY_ENABLE_ENCRYPTION": "Enable network encryption (true/false)",
  386. "TRIXY_ENABLE_GPU": "Enable GPU acceleration for ML models (true/false)",
  387. "TRIXY_MAX_SATELLITES": "Maximum number of connected satellites",
  388. "TRIXY_PLUGIN_DIRECTORIES": "Comma-separated list of plugin directories",
  389. }
  390. def get_env_var_documentation() -> Dict[str, str]:
  391. """Get documentation for all supported environment variables."""
  392. return ENV_VAR_DOCS.copy()