#!/usr/bin/env python3 """ Enhanced CLI Tool for Personal Internet Cell Advanced command-line interface with interactive mode and service management """ import argparse import requests import json import sys import os import cmd from datetime import datetime from typing import Dict, List, Optional, Any import yaml from pathlib import Path # Optional readline import for better CLI experience try: import readline except ImportError: # readline not available on Windows, that's okay pass API_BASE = "http://localhost:3000/api" class APIClient: """API client for making requests to the cell API""" def __init__(self, base_url: str = API_BASE): self.base_url = base_url self.session = requests.Session() self.session.headers.update({'Content-Type': 'application/json'}) def request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Optional[Dict]: """Make API request""" url = f"{self.base_url}{endpoint}" try: if method == "GET": response = self.session.get(url) elif method == "POST": response = self.session.post(url, json=data) elif method == "PUT": response = self.session.put(url, json=data) elif method == "DELETE": response = self.session.delete(url) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f"āŒ API Error: {e}") return None class ConfigManager: """Configuration management for CLI""" def __init__(self, config_dir: str = "~/.picell"): self.config_dir = Path(config_dir).expanduser() self.config_file = self.config_dir / "cli_config.yaml" self.config_dir.mkdir(parents=True, exist_ok=True) self.config = self._load_config() def _load_config(self) -> Dict[str, Any]: """Load configuration from file""" if self.config_file.exists(): try: with open(self.config_file, 'r') as f: return yaml.safe_load(f) or {} except Exception as e: print(f"Warning: Could not load config: {e}") return {} def _save_config(self): """Save configuration to file""" try: with open(self.config_file, 'w') as f: yaml.dump(self.config, f, default_flow_style=False) except Exception as e: print(f"Warning: Could not save config: {e}") def get(self, key: str, default: Any = None) -> Any: """Get configuration value""" return self.config.get(key, default) def set(self, key: str, value: Any): """Set configuration value""" self.config[key] = value self._save_config() def export_config(self, format: str = 'json') -> str: """Export configuration""" if format == 'json': return json.dumps(self.config, indent=2) elif format == 'yaml': return yaml.dump(self.config, default_flow_style=False) else: raise ValueError(f"Unsupported format: {format}") def import_config(self, config_data: str, format: str = 'json'): """Import configuration""" try: if format == 'json': new_config = json.loads(config_data) elif format == 'yaml': new_config = yaml.safe_load(config_data) else: raise ValueError(f"Unsupported format: {format}") self.config.update(new_config) self._save_config() print("āœ… Configuration imported successfully") except Exception as e: print(f"āŒ Error importing configuration: {e}") class EnhancedCLI(cmd.Cmd): """Interactive CLI shell""" intro = """ šŸš€ Personal Internet Cell - Enhanced CLI Type 'help' for available commands or 'help ' for detailed help. Type 'exit' or 'quit' to exit. """ prompt = "picell> " def __init__(self): super().__init__() self.api_client = APIClient() self.config_manager = ConfigManager() self.current_service = None def do_status(self, arg): """Show cell status""" status = self.api_client.request("GET", "/status") if status: self._display_status(status) else: print("āŒ Failed to get status") def do_services(self, arg): """Show all services status""" services = self.api_client.request("GET", "/services/status") if services: self._display_services(services) else: print("āŒ Failed to get services status") def do_peers(self, arg): """List configured peers""" peers = self.api_client.request("GET", "/peers") if peers is not None: if not peers: print("šŸ“­ No peers configured.") return self._display_peers(peers) else: print("āŒ Failed to fetch peers") def do_add_peer(self, arg): """Add a new peer: add_peer """ args = arg.split() if len(args) != 3: print("āŒ Usage: add_peer ") return name, ip, public_key = args data = {"name": name, "ip": ip, "public_key": public_key} result = self.api_client.request("POST", "/peers", data) if result: print(f"āœ… {result.get('message', 'Peer added successfully')}") else: print("āŒ Failed to add peer") def do_remove_peer(self, arg): """Remove a peer: remove_peer """ if not arg: print("āŒ Usage: remove_peer ") return result = self.api_client.request("DELETE", f"/peers/{arg}") if result: print(f"āœ… {result.get('message', 'Peer removed successfully')}") else: print("āŒ Failed to remove peer") def do_config(self, arg): """Show cell configuration""" config = self.api_client.request("GET", "/config") if config: self._display_config(config) else: print("āŒ Failed to get configuration") def do_update_config(self, arg): """Update configuration: update_config """ args = arg.split(' ', 1) if len(args) != 2: print("āŒ Usage: update_config ") return key, value = args data = {key: value} result = self.api_client.request("PUT", "/config", data) if result: print(f"āœ… {result.get('message', 'Configuration updated')}") else: print("āŒ Failed to update configuration") def do_logs(self, arg): """Show service logs: logs [service] [lines]""" args = arg.split() service = args[0] if args else "api" lines = int(args[1]) if len(args) > 1 else 50 logs = self.api_client.request("GET", f"/logs?lines={lines}") if logs and "log" in logs: print(f"šŸ“‹ Logs for {service} (last {lines} lines):") print("-" * 50) print(logs["log"]) else: print("āŒ Failed to get logs") def do_health(self, arg): """Show health check results""" health = self.api_client.request("GET", "/health/history") if health: self._display_health(health) else: print("āŒ Failed to get health data") def do_backup(self, arg): """Create configuration backup""" backup = self.api_client.request("POST", "/config/backup") if backup: print(f"āœ… Backup created: {backup.get('backup_id', 'unknown')}") else: print("āŒ Failed to create backup") def do_restore(self, arg): """Restore configuration from backup: restore """ if not arg: print("āŒ Usage: restore ") return result = self.api_client.request("POST", f"/config/restore/{arg}") if result: print(f"āœ… Configuration restored from backup: {arg}") else: print("āŒ Failed to restore configuration") def do_backups(self, arg): """List available backups""" backups = self.api_client.request("GET", "/config/backups") if backups: self._display_backups(backups) else: print("āŒ Failed to get backups") def do_service(self, arg): """Switch to service context: service """ if not arg: print("āŒ Usage: service ") return self.current_service = arg print(f"šŸ”§ Switched to service context: {arg}") self.prompt = f"picell:{arg}> " def do_exit(self, arg): """Exit the CLI""" print("šŸ‘‹ Goodbye!") return True def do_quit(self, arg): """Exit the CLI""" return self.do_exit(arg) def do_EOF(self, arg): """Exit on EOF""" return self.do_exit(arg) def _display_status(self, status: Dict[str, Any]): """Display cell status""" print("šŸ“Š Personal Internet Cell Status") print("=" * 40) print(f"Cell Name: {status.get('cell_name', 'Unknown')}") print(f"Domain: {status.get('domain', 'Unknown')}") print(f"Peers: {status.get('peers_count', 0)}") print(f"Uptime: {status.get('uptime', 0)} seconds") print("\nšŸ”§ Services:") services = status.get('services', {}) for service, service_status in services.items(): if isinstance(service_status, dict): running = service_status.get('running', False) status_text = service_status.get('status', 'unknown') else: running = bool(service_status) status_text = 'online' if running else 'offline' status_icon = "🟢" if running else "šŸ”“" print(f" {status_icon} {service}: {status_text}") def _display_services(self, services: Dict[str, Any]): """Display services status""" print("šŸ”§ Services Status") print("=" * 40) for service, status in services.items(): if service == 'timestamp': continue if isinstance(status, dict): running = status.get('running', False) status_text = status.get('status', 'unknown') else: running = bool(status) status_text = 'online' if running else 'offline' status_icon = "🟢" if running else "šŸ”“" print(f"{status_icon} {service}: {status_text}") def _display_peers(self, peers: List[Dict[str, Any]]): """Display peers""" print("šŸ‘„ Configured Peers:") print("=" * 40) for peer in peers: print(f"Name: {peer.get('name', 'Unknown')}") print(f"IP: {peer.get('ip', 'Unknown')}") print(f"Public Key: {peer.get('public_key', 'Unknown')[:20]}...") print(f"Added: {peer.get('added_at', 'Unknown')}") print("-" * 20) def _display_config(self, config: Dict[str, Any]): """Display configuration""" print("āš™ļø Cell Configuration:") print("=" * 40) for key, value in config.items(): print(f"{key}: {value}") def _display_health(self, health: List[Dict[str, Any]]): """Display health data""" print("ā¤ļø Health Check History") print("=" * 40) for entry in health[-5:]: # Show last 5 entries timestamp = entry.get('timestamp', 'Unknown') alerts = entry.get('alerts', []) print(f"šŸ“… {timestamp}") if alerts: for alert in alerts: print(f" āš ļø {alert}") print("-" * 20) def _display_backups(self, backups: List[Dict[str, Any]]): """Display backups""" print("šŸ’¾ Available Backups:") print("=" * 40) for backup in backups: print(f"ID: {backup.get('backup_id', 'Unknown')}") print(f"Timestamp: {backup.get('timestamp', 'Unknown')}") print(f"Services: {', '.join(backup.get('services', []))}") print("-" * 20) def batch_operations(commands: List[str]): """Execute batch operations""" cli = EnhancedCLI() for command in commands: print(f"šŸ”„ Executing: {command}") cli.onecmd(command) print() def export_config(format: str = 'json') -> str: """Export configuration""" config_manager = ConfigManager() return config_manager.export_config(format) def import_config(config_file: str, format: str = 'json') -> bool: """Import configuration""" try: with open(config_file, 'r') as f: config_data = f.read() config_manager = ConfigManager() config_manager.import_config(config_data, format) return True except Exception as e: print(f"āŒ Error importing configuration: {e}") return False def service_wizard(service: str): """Interactive service configuration wizard""" print(f"šŸ”§ {service.title()} Service Configuration Wizard") print("=" * 50) config = {} if service == 'network': config['dns_port'] = input("DNS Port (default: 53): ") or 53 config['dhcp_range'] = input("DHCP Range (default: 10.0.0.100-10.0.0.200): ") or "10.0.0.100-10.0.0.200" config['ntp_servers'] = input("NTP Servers (comma-separated): ").split(',') if input("NTP Servers (comma-separated): ") else [] elif service == 'wireguard': config['port'] = int(input("WireGuard Port (default: 51820): ") or 51820) config['address'] = input("WireGuard Address (default: 10.0.0.1/24): ") or "10.0.0.1/24" print("Private key will be generated automatically") elif service == 'email': config['domain'] = input("Email Domain: ") config['smtp_port'] = int(input("SMTP Port (default: 587): ") or 587) config['imap_port'] = int(input("IMAP Port (default: 993): ") or 993) else: print(f"āŒ Wizard not available for service: {service}") return # Save configuration api_client = APIClient() result = api_client.request("PUT", f"/config/{service}", config) if result: print(f"āœ… {service.title()} configuration saved") else: print(f"āŒ Failed to save {service} configuration") def main(): """Main CLI entry point""" parser = argparse.ArgumentParser(description="Personal Internet Cell Enhanced CLI") parser.add_argument('--interactive', '-i', action='store_true', help='Start interactive mode') parser.add_argument('--batch', '-b', nargs='+', help='Execute batch commands') parser.add_argument('--export-config', choices=['json', 'yaml'], help='Export configuration') parser.add_argument('--import-config', metavar='FILE', help='Import configuration from file') parser.add_argument('--wizard', metavar='SERVICE', help='Run configuration wizard for service') parser.add_argument('--status', action='store_true', help='Show cell status') parser.add_argument('--services', action='store_true', help='Show all services status') parser.add_argument('--peers', action='store_true', help='List peers') parser.add_argument('--logs', metavar='SERVICE', help='Show service logs') parser.add_argument('--health', action='store_true', help='Show health data') args = parser.parse_args() if args.interactive: EnhancedCLI().cmdloop() elif args.batch: batch_operations(args.batch) elif args.export_config: print(export_config(args.export_config)) elif args.import_config: format = 'json' if args.import_config.endswith('.json') else 'yaml' import_config(args.import_config, format) elif args.wizard: service_wizard(args.wizard) elif args.status: cli = EnhancedCLI() cli.do_status("") elif args.services: cli = EnhancedCLI() cli.do_services("") elif args.peers: cli = EnhancedCLI() cli.do_peers("") elif args.logs: cli = EnhancedCLI() cli.do_logs(args.logs) elif args.health: cli = EnhancedCLI() cli.do_health("") else: parser.print_help() if __name__ == '__main__': main()