feat: complete monorepo structure with frontend and shared resources

- Add complete backend/ directory with full Django application
- Add frontend/ directory with Vite + TypeScript setup ready for Next.js
- Add comprehensive shared/ directory with:
  - Complete documentation and memory-bank archives
  - Media files and avatars (letters, park/ride images)
  - Deployment scripts and automation tools
  - Shared types and utilities
- Add architecture/ directory with migration guides
- Configure pnpm workspace for monorepo development
- Update .gitignore to exclude .django_tailwind_cli/ build artifacts
- Preserve all historical documentation in shared/docs/memory-bank/
- Set up proper structure for full-stack development with shared resources
This commit is contained in:
pacnpal
2025-08-23 18:40:07 -04:00
parent b0e0678590
commit d504d41de2
762 changed files with 142636 additions and 0 deletions

View File

@@ -0,0 +1,288 @@
#!/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()