setup.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. #!/usr/bin/env python3
  2. """
  3. Trixy Voice Assistant Setup Script
  4. This script provides automated installation and setup for the Trixy Voice Assistant
  5. system across different deployment modes and platforms.
  6. Usage:
  7. python setup.py --mode [client|server|standalone|dev|ml] [options]
  8. Examples:
  9. python setup.py --mode client # Minimal client installation
  10. python setup.py --mode server --with-gpu # Server with GPU support
  11. python setup.py --mode dev --install-hooks # Development environment
  12. python setup.py --mode ml --cuda-version 11.8 # ML training with CUDA
  13. """
  14. import os
  15. import sys
  16. import argparse
  17. import subprocess
  18. import platform
  19. import json
  20. from pathlib import Path
  21. from typing import List, Dict, Any, Optional
  22. class TrixySetup:
  23. """Automated setup for Trixy Voice Assistant."""
  24. def __init__(self):
  25. self.platform = platform.system().lower()
  26. self.architecture = platform.machine().lower()
  27. self.python_version = sys.version_info
  28. self.cwd = Path.cwd()
  29. # Requirements file mapping
  30. self.requirements_map = {
  31. 'client': 'requirements-client.txt',
  32. 'server': 'requirements-server.txt',
  33. 'standalone': 'requirements-server.txt', # Standalone uses server requirements
  34. 'dev': 'requirements-dev.txt',
  35. 'ml': 'requirements-ml.txt',
  36. 'base': 'requirements.txt',
  37. 'optional': 'requirements-optional.txt'
  38. }
  39. def check_python_version(self) -> bool:
  40. """Check if Python version is compatible."""
  41. if self.python_version < (3, 8):
  42. print(f"❌ Error: Python 3.8+ required, found {self.python_version.major}.{self.python_version.minor}")
  43. return False
  44. print(f"✅ Python {self.python_version.major}.{self.python_version.minor}.{self.python_version.micro} detected")
  45. return True
  46. def check_system_dependencies(self, mode: str) -> bool:
  47. """Check and install system dependencies based on platform and mode."""
  48. print(f"🔍 Checking system dependencies for {mode} mode on {self.platform}...")
  49. if self.platform == 'linux':
  50. return self._check_linux_dependencies(mode)
  51. elif self.platform == 'darwin':
  52. return self._check_macos_dependencies(mode)
  53. elif self.platform == 'windows':
  54. return self._check_windows_dependencies(mode)
  55. else:
  56. print(f"⚠️ Platform {self.platform} not fully supported")
  57. return True
  58. def _check_linux_dependencies(self, mode: str) -> bool:
  59. """Check Linux system dependencies."""
  60. required_packages = ['python3-pip', 'python3-venv']
  61. if mode in ['client', 'server', 'standalone']:
  62. required_packages.extend([
  63. 'portaudio19-dev',
  64. 'libasound2-dev',
  65. 'ffmpeg',
  66. 'libavcodec-extra'
  67. ])
  68. if mode in ['server', 'standalone']:
  69. required_packages.append('redis-server')
  70. if mode in ['dev', 'ml']:
  71. required_packages.extend([
  72. 'build-essential',
  73. 'cmake',
  74. 'git'
  75. ])
  76. print(f"📦 Required packages: {', '.join(required_packages)}")
  77. print("💡 Install with: sudo apt-get install " + " ".join(required_packages))
  78. return True
  79. def _check_macos_dependencies(self, mode: str) -> bool:
  80. """Check macOS system dependencies."""
  81. required_packages = []
  82. if mode in ['client', 'server', 'standalone']:
  83. required_packages.extend(['portaudio', 'ffmpeg'])
  84. if mode in ['server', 'standalone']:
  85. required_packages.append('redis')
  86. if required_packages:
  87. print(f"📦 Required Homebrew packages: {', '.join(required_packages)}")
  88. print("💡 Install with: brew install " + " ".join(required_packages))
  89. return True
  90. def _check_windows_dependencies(self, mode: str) -> bool:
  91. """Check Windows system dependencies."""
  92. print("📦 Windows dependencies:")
  93. print("- Visual C++ Build Tools (for some packages)")
  94. print("- FFmpeg (add to PATH)")
  95. if mode in ['server', 'standalone']:
  96. print("- Redis (optional, can use Redis Cloud)")
  97. return True
  98. def create_virtual_environment(self, venv_name: str = "trixy-env") -> bool:
  99. """Create a Python virtual environment."""
  100. venv_path = self.cwd / venv_name
  101. if venv_path.exists():
  102. print(f"✅ Virtual environment {venv_name} already exists")
  103. return True
  104. try:
  105. print(f"🔨 Creating virtual environment: {venv_name}")
  106. subprocess.run([sys.executable, '-m', 'venv', str(venv_path)], check=True)
  107. print(f"✅ Virtual environment created: {venv_path}")
  108. return True
  109. except subprocess.CalledProcessError as e:
  110. print(f"❌ Failed to create virtual environment: {e}")
  111. return False
  112. def get_pip_command(self, venv_name: str = "trixy-env") -> List[str]:
  113. """Get the pip command for the virtual environment."""
  114. venv_path = self.cwd / venv_name
  115. if self.platform == 'windows':
  116. pip_path = venv_path / "Scripts" / "pip.exe"
  117. else:
  118. pip_path = venv_path / "bin" / "pip"
  119. return [str(pip_path)]
  120. def install_pytorch(self, cuda_version: Optional[str] = None, venv_name: str = "trixy-env") -> bool:
  121. """Install PyTorch with appropriate configuration."""
  122. pip_cmd = self.get_pip_command(venv_name)
  123. if cuda_version:
  124. print(f"🔥 Installing PyTorch with CUDA {cuda_version}")
  125. index_url = f"https://download.pytorch.org/whl/cu{cuda_version.replace('.', '')}"
  126. cmd = pip_cmd + ['install', 'torch', 'torchaudio', '--index-url', index_url]
  127. elif self.architecture in ['aarch64', 'arm64']:
  128. print("🔧 Installing PyTorch for ARM architecture")
  129. cmd = pip_cmd + ['install', 'torch', 'torchaudio', '--index-url',
  130. 'https://download.pytorch.org/whl/cpu']
  131. else:
  132. print("🔧 Installing PyTorch CPU version")
  133. cmd = pip_cmd + ['install', 'torch', 'torchaudio', '--index-url',
  134. 'https://download.pytorch.org/whl/cpu']
  135. try:
  136. subprocess.run(cmd, check=True)
  137. print("✅ PyTorch installed successfully")
  138. return True
  139. except subprocess.CalledProcessError as e:
  140. print(f"❌ Failed to install PyTorch: {e}")
  141. return False
  142. def install_requirements(self, mode: str, venv_name: str = "trixy-env") -> bool:
  143. """Install requirements for the specified mode."""
  144. requirements_file = self.requirements_map.get(mode)
  145. if not requirements_file:
  146. print(f"❌ Unknown mode: {mode}")
  147. return False
  148. requirements_path = self.cwd / requirements_file
  149. if not requirements_path.exists():
  150. print(f"❌ Requirements file not found: {requirements_path}")
  151. return False
  152. pip_cmd = self.get_pip_command(venv_name)
  153. try:
  154. print(f"📦 Installing requirements from {requirements_file}")
  155. # Upgrade pip first
  156. subprocess.run(pip_cmd + ['install', '--upgrade', 'pip'], check=True)
  157. # Install requirements
  158. subprocess.run(pip_cmd + ['install', '-r', str(requirements_path)], check=True)
  159. print(f"✅ Requirements installed successfully")
  160. return True
  161. except subprocess.CalledProcessError as e:
  162. print(f"❌ Failed to install requirements: {e}")
  163. return False
  164. def setup_development_tools(self, venv_name: str = "trixy-env") -> bool:
  165. """Set up development tools like pre-commit hooks."""
  166. try:
  167. # Get the python command from virtual environment
  168. venv_path = self.cwd / venv_name
  169. if self.platform == 'windows':
  170. python_cmd = str(venv_path / "Scripts" / "python.exe")
  171. else:
  172. python_cmd = str(venv_path / "bin" / "python")
  173. print("🔧 Setting up pre-commit hooks...")
  174. subprocess.run([python_cmd, '-m', 'pre_commit', 'install'], check=True)
  175. print("✅ Pre-commit hooks installed")
  176. return True
  177. except subprocess.CalledProcessError as e:
  178. print(f"⚠️ Failed to set up pre-commit hooks: {e}")
  179. return False
  180. def verify_installation(self, mode: str, venv_name: str = "trixy-env") -> bool:
  181. """Verify the installation is working correctly."""
  182. print("🔍 Verifying installation...")
  183. venv_path = self.cwd / venv_name
  184. if self.platform == 'windows':
  185. python_cmd = str(venv_path / "Scripts" / "python.exe")
  186. else:
  187. python_cmd = str(venv_path / "bin" / "python")
  188. # Test basic imports
  189. test_commands = [
  190. "import torch; print(f'PyTorch version: {torch.__version__}')",
  191. "import torchaudio; print(f'TorchAudio version: {torchaudio.__version__}')",
  192. "import numpy; print(f'NumPy version: {numpy.__version__}')",
  193. ]
  194. if mode in ['server', 'dev']:
  195. test_commands.append("import rich; print('Rich import successful')")
  196. for test_cmd in test_commands:
  197. try:
  198. result = subprocess.run([python_cmd, '-c', test_cmd],
  199. capture_output=True, text=True, check=True)
  200. print(f"✅ {result.stdout.strip()}")
  201. except subprocess.CalledProcessError as e:
  202. print(f"❌ Test failed: {test_cmd}")
  203. print(f" Error: {e.stderr}")
  204. return False
  205. # Test main application import
  206. try:
  207. subprocess.run([python_cmd, '-c',
  208. "print('Testing main application...'); import main; print('✅ Main application import successful')"],
  209. check=True)
  210. except subprocess.CalledProcessError as e:
  211. print("⚠️ Main application test failed (expected if core modules not implemented)")
  212. return True
  213. def create_config_files(self, mode: str) -> bool:
  214. """Create default configuration files."""
  215. config_dir = self.cwd / "config"
  216. config_dir.mkdir(exist_ok=True)
  217. configs = {
  218. 'server': {
  219. 'mode': 'server',
  220. 'debug': False,
  221. 'host': '0.0.0.0',
  222. 'command_port': 2101,
  223. 'audio_input_port': 2102,
  224. 'audio_output_port': 2103,
  225. 'music_output_port': 2104,
  226. 'max_satellites': 10,
  227. 'registration_timeout': 60
  228. },
  229. 'client': {
  230. 'mode': 'client',
  231. 'debug': False,
  232. 'server_host': 'localhost',
  233. 'server_port': 2101,
  234. 'room_id': 'default',
  235. 'alias': 'client-device',
  236. 'wakeword_model': 'models/wakeword/default.pth'
  237. },
  238. 'standalone': {
  239. 'mode': 'standalone',
  240. 'debug': False,
  241. 'enable_tui': True,
  242. 'plugin_directory': 'plugins',
  243. 'model_directory': 'models'
  244. }
  245. }
  246. if mode in configs:
  247. config_file = config_dir / f"{mode}_config.json"
  248. with open(config_file, 'w') as f:
  249. json.dump(configs[mode], f, indent=2)
  250. print(f"✅ Created configuration file: {config_file}")
  251. return True
  252. def print_next_steps(self, mode: str, venv_name: str = "trixy-env"):
  253. """Print next steps for the user."""
  254. print("\n" + "="*50)
  255. print("🎉 Installation Complete!")
  256. print("="*50)
  257. # Activation command
  258. venv_path = self.cwd / venv_name
  259. if self.platform == 'windows':
  260. activate_cmd = f"{venv_path}\\Scripts\\activate"
  261. else:
  262. activate_cmd = f"source {venv_path}/bin/activate"
  263. print(f"\n📝 Next Steps:")
  264. print(f"1. Activate the virtual environment:")
  265. print(f" {activate_cmd}")
  266. print(f"\n2. Run Trixy in {mode} mode:")
  267. print(f" python main.py {mode}")
  268. if mode == 'dev':
  269. print(f"\n3. Development commands:")
  270. print(f" pytest # Run tests")
  271. print(f" black . # Format code")
  272. print(f" mypy . # Type checking")
  273. print(f" pre-commit run --all-files # Run all checks")
  274. print(f"\n📖 Documentation:")
  275. print(f" - Installation guide: INSTALL.md")
  276. print(f" - Project overview: CLAUDE.md")
  277. print(f" - Configuration: config/{mode}_config.json")
  278. print(f"\n🔧 Configuration files created in: config/")
  279. print(f"💡 Edit configuration files before first run")
  280. def main():
  281. """Main setup function."""
  282. parser = argparse.ArgumentParser(
  283. description="Trixy Voice Assistant Setup Script",
  284. formatter_class=argparse.RawDescriptionHelpFormatter,
  285. epilog="""
  286. Examples:
  287. python setup.py --mode client # Minimal client installation
  288. python setup.py --mode server --with-gpu # Server with GPU support
  289. python setup.py --mode dev --install-hooks # Development environment
  290. python setup.py --mode ml --cuda-version 11.8 # ML training with CUDA
  291. """
  292. )
  293. parser.add_argument(
  294. '--mode',
  295. choices=['client', 'server', 'standalone', 'dev', 'ml'],
  296. required=True,
  297. help='Installation mode'
  298. )
  299. parser.add_argument(
  300. '--venv-name',
  301. default='trixy-env',
  302. help='Virtual environment name (default: trixy-env)'
  303. )
  304. parser.add_argument(
  305. '--cuda-version',
  306. help='CUDA version for GPU support (e.g., 11.8)'
  307. )
  308. parser.add_argument(
  309. '--with-gpu',
  310. action='store_true',
  311. help='Install GPU support (CUDA)'
  312. )
  313. parser.add_argument(
  314. '--install-hooks',
  315. action='store_true',
  316. help='Install pre-commit hooks (for dev mode)'
  317. )
  318. parser.add_argument(
  319. '--skip-system-check',
  320. action='store_true',
  321. help='Skip system dependency checks'
  322. )
  323. parser.add_argument(
  324. '--skip-pytorch',
  325. action='store_true',
  326. help='Skip PyTorch installation (assume already installed)'
  327. )
  328. args = parser.parse_args()
  329. # Initialize setup
  330. setup = TrixySetup()
  331. print("🚀 Trixy Voice Assistant Setup")
  332. print("="*50)
  333. print(f"Mode: {args.mode}")
  334. print(f"Platform: {setup.platform} ({setup.architecture})")
  335. print(f"Python: {setup.python_version.major}.{setup.python_version.minor}.{setup.python_version.micro}")
  336. print("="*50)
  337. # Check Python version
  338. if not setup.check_python_version():
  339. sys.exit(1)
  340. # Check system dependencies
  341. if not args.skip_system_check:
  342. if not setup.check_system_dependencies(args.mode):
  343. print("⚠️ System dependency check failed. Use --skip-system-check to continue anyway.")
  344. sys.exit(1)
  345. # Create virtual environment
  346. if not setup.create_virtual_environment(args.venv_name):
  347. sys.exit(1)
  348. # Install PyTorch
  349. if not args.skip_pytorch:
  350. cuda_version = args.cuda_version if args.with_gpu or args.cuda_version else None
  351. if not setup.install_pytorch(cuda_version, args.venv_name):
  352. sys.exit(1)
  353. # Install requirements
  354. if not setup.install_requirements(args.mode, args.venv_name):
  355. sys.exit(1)
  356. # Set up development tools
  357. if args.mode == 'dev' and args.install_hooks:
  358. setup.setup_development_tools(args.venv_name)
  359. # Create configuration files
  360. setup.create_config_files(args.mode)
  361. # Verify installation
  362. if not setup.verify_installation(args.mode, args.venv_name):
  363. print("⚠️ Installation verification had issues, but you can still try running the application")
  364. # Print next steps
  365. setup.print_next_steps(args.mode, args.venv_name)
  366. if __name__ == "__main__":
  367. main()