init
This commit is contained in:
@@ -0,0 +1,478 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user