282 lines
8.1 KiB
Python
Executable File
282 lines
8.1 KiB
Python
Executable File
#!/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 <name> <ip> <provider>")
|
|
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 <server_id>")
|
|
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()
|