#!/usr/bin/env python3 """ Unraid VM Manager for ThrillWiki - Main Orchestrator Follows the Ubuntu autoinstall guide exactly: 1. Creates modified Ubuntu ISO with autoinstall configuration 2. Manages VM lifecycle on Unraid server 3. Handles ThrillWiki deployment automation """ import os import sys import logging from pathlib import Path # Import our modular components from iso_builder import UbuntuISOBuilder from vm_manager import UnraidVMManager # Configuration UNRAID_HOST = os.environ.get("UNRAID_HOST", "localhost") UNRAID_USER = os.environ.get("UNRAID_USER", "root") VM_NAME = os.environ.get("VM_NAME", "thrillwiki-vm") VM_MEMORY = int(os.environ.get("VM_MEMORY", 4096)) # MB VM_VCPUS = int(os.environ.get("VM_VCPUS", 2)) VM_DISK_SIZE = int(os.environ.get("VM_DISK_SIZE", 50)) # GB SSH_PUBLIC_KEY = os.environ.get("SSH_PUBLIC_KEY", "") # Network Configuration VM_IP = os.environ.get("VM_IP", "dhcp") VM_GATEWAY = os.environ.get("VM_GATEWAY", "192.168.20.1") VM_NETMASK = os.environ.get("VM_NETMASK", "255.255.255.0") VM_NETWORK = os.environ.get("VM_NETWORK", "192.168.20.0/24") # GitHub Configuration REPO_URL = os.environ.get("REPO_URL", "") GITHUB_USERNAME = os.environ.get("GITHUB_USERNAME", "") GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "") # Ubuntu version preference UBUNTU_VERSION = os.environ.get("UBUNTU_VERSION", "24.04") # Setup logging os.makedirs("logs", exist_ok=True) logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("logs/unraid-vm.log"), logging.StreamHandler(), ], ) logger = logging.getLogger(__name__) class ThrillWikiVMOrchestrator: """Main orchestrator for ThrillWiki VM deployment.""" def __init__(self): self.vm_manager = UnraidVMManager(VM_NAME, UNRAID_HOST, UNRAID_USER) self.iso_builder = None def create_autoinstall_user_data(self) -> str: """Create autoinstall user-data configuration.""" # Read autoinstall template template_path = Path(__file__).parent / "autoinstall-user-data.yaml" if not template_path.exists(): raise FileNotFoundError(f"Autoinstall template not found: {template_path}") with open(template_path, "r", encoding="utf-8") as f: template = f.read() # Replace placeholders using string replacement (avoiding .format() due # to curly braces in YAML) user_data = template.replace( "{SSH_PUBLIC_KEY}", SSH_PUBLIC_KEY if SSH_PUBLIC_KEY else "# No SSH key provided", ).replace("{GITHUB_REPO}", REPO_URL if REPO_URL else "") # Update network configuration based on VM_IP setting if VM_IP.lower() == "dhcp": # Keep DHCP configuration as-is pass else: # Replace with static IP configuration network_config = f"""dhcp4: false addresses: - {VM_IP}/24 gateway4: {VM_GATEWAY} nameservers: addresses: - 8.8.8.8 - 8.8.4.4""" user_data = user_data.replace("dhcp4: true", network_config) return user_data def build_autoinstall_iso(self) -> Path: """Build Ubuntu autoinstall ISO following the guide.""" logger.info("🔨 Building Ubuntu autoinstall ISO...") # Create ISO builder self.iso_builder = UbuntuISOBuilder(VM_NAME) # Create user-data configuration user_data = self.create_autoinstall_user_data() # Build autoinstall ISO iso_output_path = Path(f"/tmp/{VM_NAME}-ubuntu-autoinstall.iso") success = self.iso_builder.build_autoinstall_iso( user_data=user_data, output_path=iso_output_path, ubuntu_version=UBUNTU_VERSION, ) if not success: raise RuntimeError("Failed to build autoinstall ISO") logger.info(f"✅ Autoinstall ISO built successfully: {iso_output_path}") return iso_output_path def deploy_vm(self) -> bool: """Complete VM deployment process.""" try: logger.info("🚀 Starting ThrillWiki VM deployment...") # Step 1: Check SSH connectivity logger.info("📡 Testing Unraid connectivity...") if not self.vm_manager.authenticate(): logger.error("❌ Cannot connect to Unraid server") return False # Step 2: Build autoinstall ISO logger.info("🔨 Building Ubuntu autoinstall ISO...") iso_path = self.build_autoinstall_iso() # Step 3: Upload ISO to Unraid logger.info("📤 Uploading autoinstall ISO to Unraid...") self.vm_manager.upload_iso_to_unraid(iso_path) # Step 4: Create/update VM configuration logger.info("⚙️ Creating VM configuration...") success = self.vm_manager.create_vm( vm_memory=VM_MEMORY, vm_vcpus=VM_VCPUS, vm_disk_size=VM_DISK_SIZE, vm_ip=VM_IP, ) if not success: logger.error("❌ Failed to create VM configuration") return False # Step 5: Start VM logger.info("🟢 Starting VM...") success = self.vm_manager.start_vm() if not success: logger.error("❌ Failed to start VM") return False logger.info("🎉 VM deployment completed successfully!") logger.info("") logger.info("📋 Next Steps:") logger.info("1. VM is now booting with Ubuntu autoinstall") logger.info("2. Installation will take 15-30 minutes") logger.info("3. Use 'python main.py ip' to get VM IP when ready") logger.info("4. SSH to VM and run /home/thrillwiki/deploy-thrillwiki.sh") logger.info("") return True except Exception as e: logger.error(f"❌ VM deployment failed: {e}") return False finally: # Cleanup ISO builder temp files if self.iso_builder: self.iso_builder.cleanup() def get_vm_info(self) -> dict: """Get VM information.""" return { "name": VM_NAME, "status": self.vm_manager.vm_status(), "ip": self.vm_manager.get_vm_ip(), "memory": VM_MEMORY, "vcpus": VM_VCPUS, "disk_size": VM_DISK_SIZE, } def main(): """Main entry point.""" import argparse parser = argparse.ArgumentParser( description="ThrillWiki VM Manager - Ubuntu Autoinstall on Unraid", epilog=""" Examples: python main.py setup # Complete VM setup with autoinstall python main.py start # Start existing VM python main.py ip # Get VM IP address python main.py status # Get VM status python main.py delete # Remove VM completely """, formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "action", choices=[ "setup", "create", "start", "stop", "status", "ip", "delete", "info", ], help="Action to perform", ) args = parser.parse_args() # Create orchestrator orchestrator = ThrillWikiVMOrchestrator() if args.action == "setup": logger.info("🚀 Setting up complete ThrillWiki VM environment...") success = orchestrator.deploy_vm() sys.exit(0 if success else 1) elif args.action == "create": logger.info("⚙️ Creating VM configuration...") success = orchestrator.vm_manager.create_vm( VM_MEMORY, VM_VCPUS, VM_DISK_SIZE, VM_IP ) sys.exit(0 if success else 1) elif args.action == "start": logger.info("🟢 Starting VM...") success = orchestrator.vm_manager.start_vm() sys.exit(0 if success else 1) elif args.action == "stop": logger.info("🛑 Stopping VM...") success = orchestrator.vm_manager.stop_vm() sys.exit(0 if success else 1) elif args.action == "status": status = orchestrator.vm_manager.vm_status() print(f"VM Status: {status}") sys.exit(0) elif args.action == "ip": ip = orchestrator.vm_manager.get_vm_ip() if ip: print(f"VM IP: {ip}") print(f"SSH: ssh thrillwiki@{ip}") print( f"Deploy: ssh thrillwiki@{ip} '/home/thrillwiki/deploy-thrillwiki.sh'" ) sys.exit(0) else: print("❌ Failed to get VM IP (VM may not be ready yet)") sys.exit(1) elif args.action == "info": info = orchestrator.get_vm_info() print("🖥️ VM Information:") print(f" Name: {info['name']}") print(f" Status: {info['status']}") print(f" IP: {info['ip'] or 'Not available'}") print(f" Memory: {info['memory']} MB") print(f" vCPUs: {info['vcpus']}") print(f" Disk: {info['disk_size']} GB") sys.exit(0) elif args.action == "delete": logger.info("🗑️ Deleting VM and all files...") success = orchestrator.vm_manager.delete_vm() sys.exit(0 if success else 1) if __name__ == "__main__": main()