#!/usr/bin/env python3 """ Proxmox Review and Deployment Planning Script Connects to both Proxmox instances, reviews configurations, checks status, and generates a deployment plan with detailed task list. """ import os import sys import json import requests from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Tuple from urllib.parse import urlparse # Try to import proxmoxer, but fall back to direct API calls if not available try: from proxmoxer import ProxmoxAPI PROXMOXER_AVAILABLE = True except ImportError: PROXMOXER_AVAILABLE = False print("Warning: proxmoxer not installed. Using direct API calls.") print("Install with: pip install proxmoxer") # Colors for terminal output class Colors: RED = '\033[0;31m' GREEN = '\033[0;32m' YELLOW = '\033[1;33m' BLUE = '\033[0;34m' CYAN = '\033[0;36m' NC = '\033[0m' # No Color def log(message: str, color: str = Colors.BLUE): """Print a log message with timestamp.""" timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"{color}[{timestamp}]{Colors.NC} {message}") def log_success(message: str): """Print a success message.""" log(f"✅ {message}", Colors.GREEN) def log_warning(message: str): """Print a warning message.""" log(f"⚠️ {message}", Colors.YELLOW) def log_error(message: str): """Print an error message.""" log(f"❌ {message}", Colors.RED) class ProxmoxClient: """Proxmox API client.""" def __init__(self, api_url: str, username: str = None, password: str = None, token: str = None, verify_ssl: bool = True): self.api_url = api_url.rstrip('/') self.username = username self.password = password self.token = token self.verify_ssl = verify_ssl self.session = requests.Session() self.ticket = None self.csrf_token = None if not verify_ssl: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def authenticate(self) -> bool: """Authenticate to Proxmox API.""" if self.token: # Token authentication self.session.headers['Authorization'] = f'PVEAuthCookie={self.token}' return True if not self.username or not self.password: log_error("Username/password or token required") return False # Password authentication auth_url = f"{self.api_url}/api2/json/access/ticket" try: response = self.session.post( auth_url, data={'username': self.username, 'password': self.password}, verify=self.verify_ssl, timeout=10 ) response.raise_for_status() data = response.json()['data'] self.ticket = data['ticket'] self.csrf_token = data.get('CSRFPreventionToken', '') self.session.headers['Cookie'] = f'PVEAuthCookie={self.ticket}' if self.csrf_token: self.session.headers['CSRFPreventionToken'] = self.csrf_token return True except Exception as e: log_error(f"Authentication failed: {e}") return False def api_call(self, endpoint: str, method: str = 'GET', data: dict = None) -> Optional[dict]: """Make an API call to Proxmox.""" url = f"{self.api_url}/api2/json{endpoint}" try: if method == 'GET': response = self.session.get(url, verify=self.verify_ssl, timeout=10) elif method == 'POST': response = self.session.post(url, json=data, verify=self.verify_ssl, timeout=10) elif method == 'PUT': response = self.session.put(url, json=data, verify=self.verify_ssl, timeout=10) elif method == 'DELETE': response = self.session.delete(url, verify=self.verify_ssl, timeout=10) else: log_error(f"Unsupported HTTP method: {method}") return None response.raise_for_status() return response.json().get('data') except Exception as e: log_error(f"API call failed ({endpoint}): {e}") return None def get_version(self) -> Optional[dict]: """Get Proxmox version information.""" return self.api_call('/version') def get_cluster_status(self) -> Optional[List[dict]]: """Get cluster status.""" return self.api_call('/cluster/status') def get_nodes(self) -> Optional[List[dict]]: """Get list of nodes.""" return self.api_call('/nodes') def get_node_status(self, node: str) -> Optional[dict]: """Get status of a specific node.""" return self.api_call(f'/nodes/{node}/status') def get_storage(self) -> Optional[List[dict]]: """Get storage information.""" return self.api_call('/storage') def get_vms(self, node: str) -> Optional[List[dict]]: """Get VMs on a node.""" return self.api_call(f'/nodes/{node}/qemu') def get_networks(self, node: str) -> Optional[List[dict]]: """Get network configuration for a node.""" return self.api_call(f'/nodes/{node}/network') def load_environment(): """Load environment variables.""" env_file = Path(__file__).parent.parent / '.env' env_vars = {} if env_file.exists(): with open(env_file) as f: for line in f: line = line.strip() if line and not line.startswith('#') and '=' in line: key, value = line.split('=', 1) env_vars[key.strip()] = value.strip() # Set defaults config = { 'proxmox_1': { 'api_url': env_vars.get('PROXMOX_1_API_URL', 'https://192.168.11.10:8006'), 'user': env_vars.get('PROXMOX_1_USER', 'root'), 'password': env_vars.get('PROXMOX_1_PASS', ''), 'token': env_vars.get('PROXMOX_1_API_TOKEN', ''), 'verify_ssl': env_vars.get('PROXMOX_1_INSECURE_SKIP_TLS_VERIFY', 'false').lower() != 'true' }, 'proxmox_2': { 'api_url': env_vars.get('PROXMOX_2_API_URL', 'https://192.168.11.11:8006'), 'user': env_vars.get('PROXMOX_2_USER', 'root'), 'password': env_vars.get('PROXMOX_2_PASS', ''), 'token': env_vars.get('PROXMOX_2_API_TOKEN', ''), 'verify_ssl': env_vars.get('PROXMOX_2_INSECURE_SKIP_TLS_VERIFY', 'false').lower() != 'true' } } return config def connect_and_review(client: ProxmoxClient, instance_num: int, output_dir: Path) -> Optional[dict]: """Connect to Proxmox and gather information.""" log(f"Connecting to Proxmox Instance {instance_num}...") if not client.authenticate(): log_error(f"Failed to authenticate to Instance {instance_num}") return None log_success(f"Authenticated to Instance {instance_num}") # Gather information info = { 'instance': instance_num, 'timestamp': datetime.now().isoformat(), 'version': client.get_version(), 'cluster_status': client.get_cluster_status(), 'nodes': client.get_nodes(), 'storage': client.get_storage() } # Get detailed node information if info['nodes']: log(f" Found {len(info['nodes'])} nodes") for node_data in info['nodes']: node = node_data.get('node', 'unknown') log(f" - {node}") # Get node status node_status = client.get_node_status(node) if node_status: info[f'node_{node}_status'] = node_status # Get VMs vms = client.get_vms(node) if vms: info[f'node_{node}_vms'] = vms log(f" VMs: {len(vms)}") # Get networks networks = client.get_networks(node) if networks: info[f'node_{node}_networks'] = networks # Save to file output_file = output_dir / f"proxmox-{instance_num}-status-{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(output_file, 'w') as f: json.dump(info, f, indent=2) log_success(f"Status saved to {output_file}") # Display summary if info.get('version'): version = info['version'].get('version', 'unknown') log(f" Version: {version}") return info def review_configurations(project_root: Path, output_dir: Path) -> str: """Review and document configurations.""" log("Reviewing configurations...") timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') config_file = output_dir / f"configuration-review-{timestamp}.md" content = [] content.append("# Proxmox Configuration Review\n") content.append(f"Generated: {datetime.now().isoformat()}\n") # Environment configuration content.append("## Environment Configuration\n") content.append("### Proxmox Instance 1\n") content.append("- API URL: From .env (PROXMOX_1_API_URL)\n") content.append("- User: From .env (PROXMOX_1_USER)\n") content.append("\n### Proxmox Instance 2\n") content.append("- API URL: From .env (PROXMOX_2_API_URL)\n") content.append("- User: From .env (PROXMOX_2_USER)\n") content.append("\n") # Provider config provider_config = project_root / "crossplane-provider-proxmox" / "examples" / "provider-config.yaml" if provider_config.exists(): content.append("## Crossplane Provider Configuration\n") content.append("```yaml\n") with open(provider_config) as f: content.append(f.read()) content.append("```\n\n") # Cloudflare tunnel configs tunnel_configs_dir = project_root / "cloudflare" / "tunnel-configs" if tunnel_configs_dir.exists(): content.append("## Cloudflare Tunnel Configurations\n") for config_file in sorted(tunnel_configs_dir.glob("proxmox-site-*.yaml")): content.append(f"### {config_file.name}\n") content.append("```yaml\n") with open(config_file) as f: content.append(f.read()) content.append("```\n\n") with open(config_file, 'w') as f: f.write(''.join(content)) log_success(f"Configuration review saved to {config_file}") return str(config_file) def generate_deployment_plan(output_dir: Path, config: dict) -> str: """Generate deployment plan.""" log("Generating deployment plan...") timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') plan_file = output_dir / f"deployment-plan-{timestamp}.md" content = [] content.append("# Proxmox Deployment Plan\n") content.append(f"Generated: {datetime.now().isoformat()}\n") content.append("## Current Status\n") content.append(f"- **Instance 1**: {config['proxmox_1']['api_url']}\n") content.append(f"- **Instance 2**: {config['proxmox_2']['api_url']}\n") content.append("\n## Deployment Phases\n") content.append("### Phase 1: Connection and Validation\n") content.append("1. Verify connectivity to both instances\n") content.append("2. Review cluster status and node health\n") content.append("3. Review storage and network configuration\n") content.append("\n### Phase 2: Configuration Alignment\n") content.append("1. Map instances to sites\n") content.append("2. Set up authentication (API tokens)\n") content.append("3. Configure Cloudflare tunnels\n") content.append("\n### Phase 3: Crossplane Provider Deployment\n") content.append("1. Complete API client implementation\n") content.append("2. Build and deploy provider\n") content.append("3. Configure ProviderConfig\n") content.append("\n### Phase 4: Infrastructure Deployment\n") content.append("1. Deploy test VMs\n") content.append("2. Set up monitoring\n") content.append("3. Configure backups\n") content.append("\n### Phase 5: Production Readiness\n") content.append("1. Security hardening\n") content.append("2. Documentation\n") content.append("3. Testing and validation\n") with open(plan_file, 'w') as f: f.write(''.join(content)) log_success(f"Deployment plan saved to {plan_file}") return str(plan_file) def generate_task_list(output_dir: Path, config: dict) -> str: """Generate detailed task list.""" log("Generating task list...") timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') task_file = output_dir / f"task-list-{timestamp}.md" content = [] content.append("# Proxmox Deployment Task List\n") content.append(f"Generated: {datetime.now().isoformat()}\n") content.append("## Immediate Tasks (Priority: High)\n") content.append("### Connection and Authentication\n") content.append("- [ ] **TASK-001**: Verify connectivity to Instance 1\n") content.append(f" - URL: {config['proxmox_1']['api_url']}\n") content.append("- [ ] **TASK-002**: Verify connectivity to Instance 2\n") content.append(f" - URL: {config['proxmox_2']['api_url']}\n") content.append("- [ ] **TASK-003**: Test authentication to Instance 1\n") content.append("- [ ] **TASK-004**: Test authentication to Instance 2\n") content.append("\n### Configuration Review\n") content.append("- [ ] **TASK-005**: Review provider-config.yaml\n") content.append("- [ ] **TASK-006**: Review Cloudflare tunnel configs\n") content.append("- [ ] **TASK-007**: Map instances to sites\n") content.append("\n## Short-term Tasks (Priority: Medium)\n") content.append("### Crossplane Provider\n") content.append("- [ ] **TASK-008**: Complete Proxmox API client implementation\n") content.append("- [ ] **TASK-009**: Build and test provider\n") content.append("- [ ] **TASK-010**: Deploy provider to Kubernetes\n") content.append("- [ ] **TASK-011**: Create ProviderConfig resource\n") content.append("\n### Infrastructure Setup\n") content.append("- [ ] **TASK-012**: Deploy Prometheus exporters\n") content.append("- [ ] **TASK-013**: Configure Cloudflare tunnels\n") content.append("- [ ] **TASK-014**: Set up monitoring dashboards\n") content.append("\n## Long-term Tasks (Priority: Low)\n") content.append("- [ ] **TASK-015**: Deploy test VMs\n") content.append("- [ ] **TASK-016**: End-to-end testing\n") content.append("- [ ] **TASK-017**: Performance testing\n") content.append("- [ ] **TASK-018**: Create runbooks\n") content.append("- [ ] **TASK-019**: Set up backups\n") content.append("- [ ] **TASK-020**: Security audit\n") with open(task_file, 'w') as f: f.write(''.join(content)) log_success(f"Task list saved to {task_file}") return str(task_file) def main(): """Main execution.""" log("Starting Proxmox Review and Deployment Planning...") log("=" * 50) project_root = Path(__file__).parent.parent output_dir = project_root / "docs" / "proxmox-review" output_dir.mkdir(parents=True, exist_ok=True) config = load_environment() log("\n=== Phase 1: Connecting to Proxmox Instances ===") # Connect to Instance 1 client1 = ProxmoxClient( config['proxmox_1']['api_url'], config['proxmox_1']['user'], config['proxmox_1']['password'], config['proxmox_1']['token'], config['proxmox_1']['verify_ssl'] ) info1 = connect_and_review(client1, 1, output_dir) log("") # Connect to Instance 2 client2 = ProxmoxClient( config['proxmox_2']['api_url'], config['proxmox_2']['user'], config['proxmox_2']['password'], config['proxmox_2']['token'], config['proxmox_2']['verify_ssl'] ) info2 = connect_and_review(client2, 2, output_dir) log("\n=== Phase 2: Reviewing Configurations ===") review_configurations(project_root, output_dir) log("\n=== Phase 3: Generating Deployment Plan ===") generate_deployment_plan(output_dir, config) log("\n=== Phase 4: Generating Task List ===") generate_task_list(output_dir, config) log("\n" + "=" * 50) log_success("Review and planning completed!") log(f"\nOutput directory: {output_dir}") if __name__ == '__main__': main()