Files
proxmox/scripts/cloudflare-setup-web.py

438 lines
15 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
Cloudflare Credentials Setup Web Interface
Provides a web UI to configure Cloudflare API credentials
"""
import os
import json
import subprocess
import re
from pathlib import Path
from flask import Flask, render_template_string, request, jsonify, redirect, url_for
app = Flask(__name__)
SCRIPT_DIR = Path(__file__).parent.parent
ENV_FILE = SCRIPT_DIR / ".env"
# HTML Template
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloudflare Credentials Setup</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 600px;
width: 100%;
padding: 40px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"], input[type="email"], input[type="password"] {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
input:focus {
outline: none;
border-color: #667eea;
}
.help-text {
font-size: 12px;
color: #888;
margin-top: 4px;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 14px 28px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn-secondary {
background: #6c757d;
margin-top: 10px;
}
.alert {
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.status-section {
background: #f8f9fa;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.status-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #e0e0e0;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
color: #666;
font-size: 14px;
}
.status-value {
color: #333;
font-weight: 500;
font-size: 14px;
}
.status-value.valid {
color: #28a745;
}
.status-value.invalid {
color: #dc3545;
}
.links {
margin-top: 20px;
text-align: center;
}
.links a {
color: #667eea;
text-decoration: none;
font-size: 13px;
margin: 0 10px;
}
.links a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>🔐 Cloudflare Setup</h1>
<p class="subtitle">Configure your Cloudflare API credentials</p>
{% if message %}
<div class="alert alert-{{ message_type }}">
{{ message }}
</div>
{% endif %}
<div class="status-section">
<h3 style="margin-bottom: 15px; font-size: 16px; color: #333;">Current Status</h3>
<div class="status-item">
<span class="status-label">Email:</span>
<span class="status-value {% if current_email %}valid{% else %}invalid{% endif %}">
{{ current_email or 'Not set' }}
</span>
</div>
<div class="status-item">
<span class="status-label">API Key:</span>
<span class="status-value {% if has_api_key %}valid{% else %}invalid{% endif %}">
{{ 'Set' if has_api_key else 'Not set' }}
</span>
</div>
<div class="status-item">
<span class="status-label">API Token:</span>
<span class="status-value {% if has_api_token %}valid{% else %}invalid{% endif %}">
{{ 'Set' if has_api_token else 'Not set' }}
</span>
</div>
<div class="status-item">
<span class="status-label">Account ID:</span>
<span class="status-value {% if current_account_id %}valid{% else %}invalid{% endif %}">
{{ current_account_id or 'Not set' }}
</span>
</div>
<div class="status-item">
<span class="status-label">Zone ID:</span>
<span class="status-value {% if current_zone_id %}valid{% else %}invalid{% endif %}">
{{ current_zone_id or 'Not set' }}
</span>
</div>
<div class="status-item">
<span class="status-label">Domain:</span>
<span class="status-value {% if current_domain %}valid{% else %}invalid{% endif %}">
{{ current_domain or 'Not set' }}
</span>
</div>
</div>
<form method="POST" action="/save">
<div class="form-group">
<label for="email">Cloudflare Email *</label>
<input type="email" id="email" name="email" value="{{ current_email or '' }}" required>
<div class="help-text">Your Cloudflare account email address</div>
</div>
<div class="form-group">
<label for="api_key">Global API Key</label>
<input type="password" id="api_key" name="api_key" placeholder="Leave empty to keep current">
<div class="help-text">
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Get from Cloudflare Dashboard</a>
</div>
</div>
<div class="form-group">
<label for="api_token">API Token (Alternative)</label>
<input type="password" id="api_token" name="api_token" placeholder="Leave empty to keep current">
<div class="help-text">
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Create API Token</a> (Recommended - more secure)
</div>
</div>
<div class="form-group">
<label for="account_id">Account ID</label>
<input type="text" id="account_id" name="account_id" value="{{ current_account_id or '' }}">
<div class="help-text">Your Cloudflare account ID</div>
</div>
<div class="form-group">
<label for="zone_id">Zone ID</label>
<input type="text" id="zone_id" name="zone_id" value="{{ current_zone_id or '' }}">
<div class="help-text">DNS zone ID for your domain</div>
</div>
<div class="form-group">
<label for="domain">Domain</label>
<input type="text" id="domain" name="domain" value="{{ current_domain or '' }}" placeholder="example.com">
<div class="help-text">Your domain name (e.g., d-bis.org)</div>
</div>
<div class="form-group">
<label for="tunnel_token">Tunnel Token</label>
<input type="password" id="tunnel_token" name="tunnel_token" placeholder="Leave empty to keep current">
<div class="help-text">Cloudflare Tunnel service token</div>
</div>
<button type="submit" class="btn">💾 Save Credentials</button>
</form>
<form method="POST" action="/test" style="margin-top: 10px;">
<button type="submit" class="btn btn-secondary">🧪 Test API Connection</button>
</form>
<div class="links">
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Cloudflare API Tokens</a>
<a href="https://dash.cloudflare.com/" target="_blank">Cloudflare Dashboard</a>
</div>
</div>
</body>
</html>
"""
def load_env():
"""Load current .env file values"""
env_vars = {}
if ENV_FILE.exists():
with open(ENV_FILE, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
env_vars[key] = value
return env_vars
def save_env(env_vars):
"""Save environment variables to .env file"""
# Read existing file to preserve comments and structure
lines = []
if ENV_FILE.exists():
with open(ENV_FILE, 'r') as f:
lines = f.readlines()
# Update or add variables
updated_keys = set()
new_lines = []
for line in lines:
stripped = line.strip()
if stripped and not stripped.startswith('#') and '=' in stripped:
key = stripped.split('=', 1)[0].strip()
if key in env_vars:
new_lines.append(f'{key}="{env_vars[key]}"\n')
updated_keys.add(key)
continue
new_lines.append(line)
# Add new variables
for key, value in env_vars.items():
if key not in updated_keys:
new_lines.append(f'{key}="{value}"\n')
with open(ENV_FILE, 'w') as f:
f.writelines(new_lines)
def test_api_credentials(email, api_key, api_token):
"""Test Cloudflare API credentials"""
import requests
headers = {"Content-Type": "application/json"}
if api_token:
headers["Authorization"] = f"Bearer {api_token}"
url = "https://api.cloudflare.com/client/v4/user"
elif api_key and email:
headers["X-Auth-Email"] = email
headers["X-Auth-Key"] = api_key
url = "https://api.cloudflare.com/client/v4/user"
else:
return False, "No credentials provided"
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get('success'):
user_email = data.get('result', {}).get('email', '')
return True, f"✓ Authentication successful! Logged in as: {user_email}"
else:
error = data.get('errors', [{}])[0].get('message', 'Unknown error')
return False, f"✗ Authentication failed: {error}"
except Exception as e:
return False, f"✗ Connection error: {str(e)}"
@app.route('/')
def index():
"""Display the setup form"""
env = load_env()
return render_template_string(HTML_TEMPLATE,
current_email=env.get('CLOUDFLARE_EMAIL', ''),
has_api_key=bool(env.get('CLOUDFLARE_API_KEY', '')),
has_api_token=bool(env.get('CLOUDFLARE_API_TOKEN', '')),
current_account_id=env.get('CLOUDFLARE_ACCOUNT_ID', ''),
current_zone_id=env.get('CLOUDFLARE_ZONE_ID', ''),
current_domain=env.get('CLOUDFLARE_DOMAIN', ''),
message=request.args.get('message', ''),
message_type=request.args.get('type', 'info')
)
@app.route('/save', methods=['POST'])
def save():
"""Save credentials to .env file"""
env = load_env()
# Update only provided fields
if request.form.get('email'):
env['CLOUDFLARE_EMAIL'] = request.form.get('email')
if request.form.get('api_key'):
env['CLOUDFLARE_API_KEY'] = request.form.get('api_key')
if request.form.get('api_token'):
env['CLOUDFLARE_API_TOKEN'] = request.form.get('api_token')
# Remove API key if token is provided
if 'CLOUDFLARE_API_KEY' in env:
del env['CLOUDFLARE_API_KEY']
if request.form.get('account_id'):
env['CLOUDFLARE_ACCOUNT_ID'] = request.form.get('account_id')
if request.form.get('zone_id'):
env['CLOUDFLARE_ZONE_ID'] = request.form.get('zone_id')
if request.form.get('domain'):
env['CLOUDFLARE_DOMAIN'] = request.form.get('domain')
if request.form.get('tunnel_token'):
env['CLOUDFLARE_TUNNEL_TOKEN'] = request.form.get('tunnel_token')
save_env(env)
return redirect(url_for('index', message='Credentials saved successfully!', type='success'))
@app.route('/test', methods=['POST'])
def test():
"""Test API credentials"""
env = load_env()
email = request.form.get('email') or env.get('CLOUDFLARE_EMAIL', '')
api_key = request.form.get('api_key') or env.get('CLOUDFLARE_API_KEY', '')
api_token = request.form.get('api_token') or env.get('CLOUDFLARE_API_TOKEN', '')
if not email and not api_token:
return redirect(url_for('index', message='Please provide email and API key, or API token', type='error'))
success, message = test_api_credentials(email, api_key, api_token)
return redirect(url_for('index', message=message, type='success' if success else 'error'))
if __name__ == '__main__':
print("\n" + "="*60)
print("🌐 Cloudflare Credentials Setup Web Interface")
print("="*60)
print(f"\n📁 .env file location: {ENV_FILE}")
print(f"\n🔗 Open in your browser:")
print(f" http://localhost:5000")
print(f" http://127.0.0.1:5000")
print(f"\n⚠️ This server is only accessible from localhost")
print(f" Press Ctrl+C to stop the server\n")
print("="*60 + "\n")
app.run(host='127.0.0.1', port=5000, debug=False)