#!/usr/bin/env python3 """ SERVER MANAGER Gestion automatique des serveurs PMTA - Création sur providers cloud - Destruction si blacklisté - Rotation d'IPs """ import psycopg2 import requests import json import sys from datetime import datetime, timedelta DB_CONFIG = { 'host': 'localhost', 'database': 'adx_system', 'user': 'admin', 'password': 'admin123' } def get_db(): return psycopg2.connect(**DB_CONFIG) def get_provider_api(provider_name): """Get provider API config""" conn = get_db() cur = conn.cursor() cur.execute(""" SELECT api_url, api_key, api_secret, default_region FROM admin.cloud_providers WHERE name = %s AND is_active = true """, (provider_name,)) result = cur.fetchone() conn.close() return result def create_server_hetzner(name, server_type='cx11'): """Create server on Hetzner""" api = get_provider_api('hetzner') if not api or not api[1]: return {'error': 'Hetzner API not configured'} api_url, api_key, _, region = api headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' } data = { 'name': name, 'server_type': server_type, 'location': region or 'hel1', 'image': 'ubuntu-22.04', 'start_after_create': True } try: response = requests.post( 'https://api.hetzner.cloud/v1/servers', headers=headers, json=data, timeout=60 ) if response.status_code == 201: result = response.json() return { 'success': True, 'server_id': result['server']['id'], 'ip': result['server']['public_net']['ipv4']['ip'], 'status': result['server']['status'] } else: return {'error': response.text} except Exception as e: return {'error': str(e)} def create_server_scaleway(name, server_type='DEV1-S'): """Create server on Scaleway""" api = get_provider_api('scaleway') if not api or not api[1]: return {'error': 'Scaleway API not configured'} # Similar implementation... return {'error': 'Not implemented yet'} def destroy_server(server_id, provider_name): """Destroy a server""" conn = get_db() cur = conn.cursor() # Get server info cur.execute("SELECT server_id FROM admin.servers WHERE id = %s", (server_id,)) result = cur.fetchone() if not result: return {'error': 'Server not found'} provider_server_id = result[0] if provider_name == 'hetzner': api = get_provider_api('hetzner') if api and api[1]: headers = {'Authorization': f'Bearer {api[1]}'} try: response = requests.delete( f'https://api.hetzner.cloud/v1/servers/{provider_server_id}', headers=headers, timeout=30 ) success = response.status_code in [200, 204] except Exception as e: return {'error': str(e)} else: success = True # Manual for other providers if success: cur.execute(""" UPDATE admin.servers SET status = 'destroyed', destroyed_at = NOW() WHERE id = %s """, (server_id,)) cur.execute(""" INSERT INTO admin.server_actions (server_id, action, triggered_by, success) VALUES (%s, 'destroy', 'server_manager', true) """, (server_id,)) conn.commit() conn.close() return {'success': success} def register_server(name, ip, provider_name, server_type='pmta', specs=None): """Register existing server in database""" conn = get_db() cur = conn.cursor() cur.execute(""" INSERT INTO admin.servers (name, ip_address, provider_name, server_type, specs, status, started_at) VALUES (%s, %s, %s, %s, %s, 'running', NOW()) RETURNING id """, (name, ip, provider_name, server_type, json.dumps(specs or {}))) server_id = cur.fetchone()[0] cur.execute(""" INSERT INTO admin.server_actions (server_id, action, triggered_by, success) VALUES (%s, 'register', 'manual', true) """, (server_id,)) conn.commit() conn.close() return {'success': True, 'server_id': server_id} def list_servers(status=None): """List all servers""" conn = get_db() cur = conn.cursor() query = """ SELECT id, name, ip_address, provider_name, status, started_at, total_sent, ip_reputation_score, is_blacklisted FROM admin.servers """ if status: query += f" WHERE status = '{status}'" query += " ORDER BY started_at DESC" cur.execute(query) servers = cur.fetchall() conn.close() return servers def auto_cleanup_burned(): """Destroy all burned servers""" conn = get_db() cur = conn.cursor() cur.execute(""" SELECT id, name, provider_name, ip_address FROM admin.servers WHERE status = 'burned' AND destroyed_at IS NULL """) burned = cur.fetchall() conn.close() print(f"Found {len(burned)} burned servers to destroy") for server_id, name, provider, ip in burned: print(f" Destroying {name} ({ip})...") result = destroy_server(server_id, provider) if result.get('success'): print(f" ✅ Destroyed") else: print(f" ❌ Error: {result.get('error')}") return len(burned) def show_status(): """Show server fleet status""" servers = list_servers() print("=" * 80) print("SERVER FLEET STATUS") print("=" * 80) by_status = {} by_provider = {} total_sent = 0 for s in servers: sid, name, ip, provider, status, started, sent, rep, blacklisted = s by_status[status] = by_status.get(status, 0) + 1 by_provider[provider] = by_provider.get(provider, 0) + 1 total_sent += sent or 0 print(f"\nTotal servers: {len(servers)}") print(f"Total emails sent: {total_sent:,}") print("\nBy Status:") for status, count in sorted(by_status.items()): emoji = {'running': '🟢', 'burned': '🔴', 'stopped': '🟡', 'destroyed': '⚫'}.get(status, '⚪') print(f" {emoji} {status}: {count}") print("\nBy Provider:") for provider, count in sorted(by_provider.items()): print(f" {provider}: {count}") print("\n" + "-" * 80) print(f"{'ID':<4} {'Name':<20} {'IP':<16} {'Provider':<10} {'Status':<10} {'Sent':<8} {'Rep':<4} {'BL'}") print("-" * 80) for s in servers[:20]: # Show top 20 sid, name, ip, provider, status, started, sent, rep, blacklisted = s bl_mark = "🚨" if blacklisted else "✅" print(f"{sid:<4} {name[:20]:<20} {str(ip):<16} {provider or '-':<10} {status:<10} {sent or 0:<8} {rep or 0:<4} {bl_mark}") def main(): if len(sys.argv) < 2: show_status() return cmd = sys.argv[1] if cmd == 'status': show_status() elif cmd == 'list': status = sys.argv[2] if len(sys.argv) > 2 else None servers = list_servers(status) for s in servers: print(s) elif cmd == 'register': if len(sys.argv) < 5: print("Usage: server-manager.py register ") return name, ip, provider = sys.argv[2], sys.argv[3], sys.argv[4] result = register_server(name, ip, provider) print(result) elif cmd == 'destroy': if len(sys.argv) < 3: print("Usage: server-manager.py destroy ") return server_id = int(sys.argv[2]) result = destroy_server(server_id, 'manual') print(result) elif cmd == 'cleanup': auto_cleanup_burned() else: print(f"Unknown command: {cmd}") print("Commands: status, list, register, destroy, cleanup") if __name__ == '__main__': main()