Files
pic/api/enhanced_cli.py
T
Constantin 2277b11563 init
2025-09-12 23:04:52 +03:00

478 lines
17 KiB
Python

#!/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 <command>' 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 <name> <ip> <public_key>"""
args = arg.split()
if len(args) != 3:
print("❌ Usage: add_peer <name> <ip> <public_key>")
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 <name>"""
if not arg:
print("❌ Usage: remove_peer <name>")
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 <key> <value>"""
args = arg.split(' ', 1)
if len(args) != 2:
print("❌ Usage: update_config <key> <value>")
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 <backup_id>"""
if not arg:
print("❌ Usage: restore <backup_id>")
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 <service_name>"""
if not arg:
print("❌ Usage: service <service_name>")
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()