Initial commit: Backend API with Cloudflare integration

This commit is contained in:
oguz ozturk 2026-01-10 13:06:24 +03:00
commit 0593965305
12 changed files with 795 additions and 0 deletions

61
.gitignore vendored Normal file
View File

@ -0,0 +1,61 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/
*.egg-info/
dist/
build/
# Flask
instance/
.webassets-cache
# Database
*.db
*.sqlite
*.sqlite3
# Environment
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Node (for frontend)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Frontend build
frontend/dist/
frontend/build/
# SSH Keys
*.pem
*.key
hosting_platform_temp
# Temporary files
backend_main.py

155
README.md Normal file
View File

@ -0,0 +1,155 @@
# 🚀 Hosting Platform - Automated DNS & SSL Management
Modern, otomatik DNS ve SSL yönetim platformu. Cloudflare entegrasyonu ile domain'leri saniyeler içinde yapılandırın.
## 📋 Özellikler
- ✅ **Cloudflare Entegrasyonu**: API token ile otomatik DNS yönetimi
- ✅ **DNS Önizleme**: Değişiklikleri uygulamadan önce görüntüleyin
- ✅ **Otomatik SSL**: Cloudflare SSL/TLS yapılandırması
- ✅ **Load Balancer**: Hash-based IP dağıtımı
- ✅ **Modern UI**: React + Vite ile hızlı ve responsive arayüz
- ✅ **Auto-Deploy**: Git push ile otomatik deployment
- ✅ **PostgreSQL**: Güvenilir veri saklama
- ✅ **Redis**: Hızlı cache ve session yönetimi
## 🏗️ Mimari
```
┌─────────────────────────────────────────────────────────┐
│ Cloudflare CDN │
│ (SSL/TLS + DDoS Protection) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Nginx Reverse Proxy │
│ (176.96.129.77 - Load Balancer) │
└─────────────────────────────────────────────────────────┘
┌──────────────┴──────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Frontend │ │ Backend │
│ React + Vite │ │ Flask API │
│ Port 3001 │ │ Port 5000 │
└───────────────┘ └───────────────┘
┌────────────────┴────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ PostgreSQL │ │ Redis │
│ Port 5432 │ │ Port 6379 │
└──────────────┘ └──────────────┘
```
## 🛠️ Teknolojiler
### Backend
- **Flask 3.0** - Modern Python web framework
- **SQLAlchemy 2.0** - ORM
- **PostgreSQL 16** - Database
- **Redis 7.0** - Cache & Sessions
- **Cloudflare API** - DNS & SSL management
### Frontend
- **React 18** - UI library
- **Vite** - Build tool
- **TailwindCSS** - Styling
- **Axios** - HTTP client
### DevOps
- **Gitea** - Git repository
- **Nginx** - Reverse proxy
- **Supervisor** - Process management
- **Systemd** - Service management
## 📦 Kurulum
### Gereksinimler
- Ubuntu 22.04+
- Python 3.12+
- Node.js 18+
- PostgreSQL 16
- Redis 7.0
### Backend Kurulumu
```bash
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Environment variables
cp .env.example .env
# .env dosyasını düzenleyin
# Database migration
flask db upgrade
# Başlatma
python app/main.py
```
### Frontend Kurulumu
```bash
cd frontend
npm install
npm run dev
```
## 🔧 Yapılandırma
### Environment Variables
```bash
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/hosting_db
# Redis
REDIS_URL=redis://localhost:6379/0
# API
API_HOST=0.0.0.0
API_PORT=5000
# Load Balancer IPs
LB_IPS=176.96.129.77,176.96.129.78,176.96.129.79
# Secret
SECRET_KEY=your-secret-key-here
```
## 🚀 API Endpoints
### Health Check
```bash
GET /health
```
### DNS Management
```bash
POST /api/dns/validate-token
POST /api/dns/preview-changes
POST /api/dns/apply-changes
```
### Domain Management
```bash
GET /api/domains
GET /api/domains/<id>
POST /api/domains
PUT /api/domains/<id>
DELETE /api/domains/<id>
```
## 📝 Lisans
MIT License - Detaylar için LICENSE dosyasına bakın.
## 👨‍💻 Geliştirici
Hosting Platform Team

0
backend/app/__init__.py Normal file
View File

View File

29
backend/app/config.py Normal file
View File

@ -0,0 +1,29 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
# Flask
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-in-production")
# Database
SQLALCHEMY_DATABASE_URI = os.getenv(
"DATABASE_URL",
"postgresql://hosting:hosting_pass_2024@localhost:5432/hosting"
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
# Redis
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
# Load Balancer IPs
LB_IPS = os.getenv("LB_IPS", "176.96.129.77").split(",")
# API
API_HOST = os.getenv("API_HOST", "0.0.0.0")
API_PORT = int(os.getenv("API_PORT", 5000))
# Cloudflare Platform Account (opsiyonel)
PLATFORM_CF_API_TOKEN = os.getenv("PLATFORM_CF_API_TOKEN")
PLATFORM_CF_ACCOUNT_ID = os.getenv("PLATFORM_CF_ACCOUNT_ID")

151
backend/app/main.py Normal file
View File

@ -0,0 +1,151 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_migrate import Migrate
import hashlib
import redis
from app.config import Config
from app.models.domain import db, Domain, DNSRecord
from app.services.cloudflare_service import CloudflareService
app = Flask(__name__)
app.config.from_object(Config)
# Extensions
CORS(app)
db.init_app(app)
migrate = Migrate(app, db)
# Redis
redis_client = redis.from_url(Config.REDIS_URL)
# Helper Functions
def select_lb_ip(domain: str) -> str:
"""Domain için load balancer IP seç (hash-based)"""
hash_value = int(hashlib.md5(domain.encode()).hexdigest(), 16)
index = hash_value % len(Config.LB_IPS)
return Config.LB_IPS[index]
# Routes
@app.route('/health', methods=['GET'])
def health():
"""Health check"""
return jsonify({"status": "ok", "service": "hosting-platform-api"})
@app.route('/api/dns/validate-token', methods=['POST'])
def validate_cf_token():
"""Cloudflare API token doğrula"""
data = request.json
domain = data.get('domain')
cf_token = data.get('cf_token')
if not domain or not cf_token:
return jsonify({"error": "domain ve cf_token gerekli"}), 400
cf_service = CloudflareService(cf_token)
result = cf_service.validate_token_and_get_zone(domain)
return jsonify(result)
@app.route('/api/dns/preview-changes', methods=['POST'])
def preview_changes():
"""DNS değişiklik önizlemesi"""
data = request.json
domain = data.get('domain')
zone_id = data.get('zone_id')
cf_token = data.get('cf_token')
if not all([domain, zone_id, cf_token]):
return jsonify({"error": "domain, zone_id ve cf_token gerekli"}), 400
# Load balancer IP seç
new_ip = select_lb_ip(domain)
cf_service = CloudflareService(cf_token)
preview = cf_service.generate_dns_preview(domain, zone_id, new_ip)
return jsonify(preview)
@app.route('/api/dns/apply-changes', methods=['POST'])
def apply_changes():
"""DNS değişikliklerini uygula"""
data = request.json
domain = data.get('domain')
zone_id = data.get('zone_id')
cf_token = data.get('cf_token')
preview = data.get('preview')
proxy_enabled = data.get('proxy_enabled', True)
customer_id = data.get('customer_id', 1) # Test için
if not all([domain, zone_id, cf_token, preview]):
return jsonify({"error": "Eksik parametreler"}), 400
cf_service = CloudflareService(cf_token)
# DNS değişikliklerini uygula
result = cf_service.apply_dns_changes(zone_id, preview, proxy_enabled)
if result["status"] == "success":
# SSL yapılandır
ssl_config = cf_service.configure_ssl(zone_id)
# Veritabanına kaydet
domain_obj = Domain.query.filter_by(domain_name=domain).first()
if not domain_obj:
domain_obj = Domain(
domain_name=domain,
customer_id=customer_id,
use_cloudflare=True,
cf_zone_id=zone_id,
cf_proxy_enabled=proxy_enabled,
lb_ip=preview["new_ip"],
status="active",
dns_configured=True,
ssl_configured=len(ssl_config["errors"]) == 0
)
db.session.add(domain_obj)
else:
domain_obj.cf_zone_id = zone_id
domain_obj.cf_proxy_enabled = proxy_enabled
domain_obj.lb_ip = preview["new_ip"]
domain_obj.status = "active"
domain_obj.dns_configured = True
domain_obj.ssl_configured = len(ssl_config["errors"]) == 0
db.session.commit()
return jsonify({
"status": "success",
"dns_result": result,
"ssl_config": ssl_config,
"domain_id": domain_obj.id
})
return jsonify(result), 500
@app.route('/api/domains', methods=['GET'])
def list_domains():
"""Domain listesi"""
customer_id = request.args.get('customer_id', 1, type=int)
domains = Domain.query.filter_by(customer_id=customer_id).all()
return jsonify([d.to_dict() for d in domains])
@app.route('/api/domains/<int:domain_id>', methods=['GET'])
def get_domain(domain_id):
"""Domain detayı"""
domain = Domain.query.get_or_404(domain_id)
return jsonify(domain.to_dict())
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host=Config.API_HOST, port=Config.API_PORT, debug=True)

View File

View File

@ -0,0 +1,84 @@
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Domain(db.Model):
__tablename__ = "domains"
id = db.Column(db.Integer, primary_key=True)
domain_name = db.Column(db.String(255), unique=True, nullable=False, index=True)
customer_id = db.Column(db.Integer, nullable=False, index=True)
# Cloudflare
use_cloudflare = db.Column(db.Boolean, default=True)
cf_zone_id = db.Column(db.String(255), nullable=True)
cf_api_token = db.Column(db.Text, nullable=True) # Encrypted
cf_proxy_enabled = db.Column(db.Boolean, default=True)
# DNS
current_ip = db.Column(db.String(45), nullable=True)
lb_ip = db.Column(db.String(45), nullable=True)
# Status
status = db.Column(db.String(50), default="pending") # pending, active, error
dns_configured = db.Column(db.Boolean, default=False)
ssl_configured = db.Column(db.Boolean, default=False)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
dns_records = db.relationship("DNSRecord", backref="domain", lazy=True, cascade="all, delete-orphan")
def to_dict(self):
return {
"id": self.id,
"domain_name": self.domain_name,
"customer_id": self.customer_id,
"use_cloudflare": self.use_cloudflare,
"cf_zone_id": self.cf_zone_id,
"cf_proxy_enabled": self.cf_proxy_enabled,
"current_ip": self.current_ip,
"lb_ip": self.lb_ip,
"status": self.status,
"dns_configured": self.dns_configured,
"ssl_configured": self.ssl_configured,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class DNSRecord(db.Model):
__tablename__ = "dns_records"
id = db.Column(db.Integer, primary_key=True)
domain_id = db.Column(db.Integer, db.ForeignKey("domains.id"), nullable=False)
record_type = db.Column(db.String(10), nullable=False) # A, CNAME, MX, TXT, etc.
name = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text, nullable=False)
ttl = db.Column(db.Integer, default=300)
proxied = db.Column(db.Boolean, default=False)
# Cloudflare
cf_record_id = db.Column(db.String(255), nullable=True)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
"id": self.id,
"domain_id": self.domain_id,
"record_type": self.record_type,
"name": self.name,
"content": self.content,
"ttl": self.ttl,
"proxied": self.proxied,
"cf_record_id": self.cf_record_id,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}

View File

View File

@ -0,0 +1,285 @@
import hashlib
from typing import Dict, List, Optional
import CloudFlare
class CloudflareService:
"""Cloudflare API işlemleri"""
def __init__(self, api_token: str):
self.cf = CloudFlare.CloudFlare(token=api_token)
self.api_token = api_token
def validate_token_and_get_zone(self, domain: str) -> Dict:
"""
API token doğrula ve zone bilgilerini al
"""
try:
# Zone ara
zones = self.cf.zones.get(params={"name": domain})
if not zones:
return {
"status": "error",
"message": f"{domain} zone bulunamadı. Domain Cloudflare hesabınızda olduğundan emin olun."
}
zone = zones[0]
zone_id = zone["id"]
# Mevcut DNS kayıtlarını al
dns_records = self.cf.zones.dns_records.get(zone_id)
return {
"status": "success",
"zone_id": zone_id,
"zone_name": zone["name"],
"zone_status": zone["status"],
"nameservers": zone.get("name_servers", []),
"account_email": zone.get("account", {}).get("email", "N/A"),
"current_dns_records": [
{
"type": r["type"],
"name": r["name"],
"content": r["content"],
"proxied": r.get("proxied", False),
"ttl": r["ttl"],
"id": r["id"]
}
for r in dns_records
]
}
except CloudFlare.exceptions.CloudFlareAPIError as e:
return {
"status": "error",
"message": f"Cloudflare API hatası: {str(e)}"
}
except Exception as e:
return {
"status": "error",
"message": f"Beklenmeyen hata: {str(e)}"
}
def generate_dns_preview(self, domain: str, zone_id: str, new_ip: str) -> Dict:
"""
DNS değişiklik önizlemesi oluştur
"""
try:
# Mevcut A kayıtlarını al
dns_records = self.cf.zones.dns_records.get(
zone_id,
params={"type": "A"}
)
current_root = None
current_www = None
for record in dns_records:
if record["name"] == domain:
current_root = record
elif record["name"] == f"www.{domain}":
current_www = record
# Önizleme oluştur
preview = {
"domain": domain,
"new_ip": new_ip,
"changes": []
}
# Root domain (@) değişikliği
if current_root:
preview["changes"].append({
"record_type": "A",
"name": "@",
"current": {
"value": current_root["content"],
"proxied": current_root.get("proxied", False),
"ttl": current_root["ttl"]
},
"new": {
"value": new_ip,
"proxied": current_root.get("proxied", True),
"ttl": "auto"
},
"action": "update",
"record_id": current_root["id"]
})
else:
preview["changes"].append({
"record_type": "A",
"name": "@",
"current": None,
"new": {
"value": new_ip,
"proxied": True,
"ttl": "auto"
},
"action": "create"
})
# www subdomain değişikliği
if current_www:
preview["changes"].append({
"record_type": "A",
"name": "www",
"current": {
"value": current_www["content"],
"proxied": current_www.get("proxied", False),
"ttl": current_www["ttl"]
},
"new": {
"value": new_ip,
"proxied": current_www.get("proxied", True),
"ttl": "auto"
},
"action": "update",
"record_id": current_www["id"]
})
else:
preview["changes"].append({
"record_type": "A",
"name": "www",
"current": None,
"new": {
"value": new_ip,
"proxied": True,
"ttl": "auto"
},
"action": "create"
})
# Diğer kayıtlar (değişmeyecek)
all_records = self.cf.zones.dns_records.get(zone_id)
other_records = [
r for r in all_records
if r["type"] != "A" or (r["name"] != domain and r["name"] != f"www.{domain}")
]
preview["preserved_records"] = [
{
"type": r["type"],
"name": r["name"],
"content": r["content"]
}
for r in other_records[:10] # İlk 10 kayıt
]
preview["preserved_count"] = len(other_records)
return preview
except Exception as e:
return {
"status": "error",
"message": f"Önizleme oluşturma hatası: {str(e)}"
}
def apply_dns_changes(self, zone_id: str, preview: Dict, proxy_enabled: bool = True) -> Dict:
"""
DNS değişikliklerini uygula
"""
results = {
"domain": preview["domain"],
"applied_changes": [],
"errors": []
}
for change in preview["changes"]:
try:
if change["action"] == "update":
# Mevcut kaydı güncelle
self.cf.zones.dns_records.patch(
zone_id,
change["record_id"],
data={
"type": "A",
"name": change["name"],
"content": change["new"]["value"],
"proxied": proxy_enabled,
"ttl": 1 if proxy_enabled else 300
}
)
results["applied_changes"].append({
"name": change["name"],
"action": "updated",
"new_value": change["new"]["value"]
})
elif change["action"] == "create":
# Yeni kayıt oluştur
self.cf.zones.dns_records.post(
zone_id,
data={
"type": "A",
"name": change["name"],
"content": change["new"]["value"],
"proxied": proxy_enabled,
"ttl": 1 if proxy_enabled else 300
}
)
results["applied_changes"].append({
"name": change["name"],
"action": "created",
"new_value": change["new"]["value"]
})
except Exception as e:
results["errors"].append({
"name": change["name"],
"error": str(e)
})
if results["errors"]:
results["status"] = "partial"
else:
results["status"] = "success"
return results
def configure_ssl(self, zone_id: str) -> Dict:
"""
Cloudflare SSL ayarlarını yapılandır
"""
ssl_config = {
"steps": [],
"errors": []
}
try:
# 1. SSL/TLS Mode: Full (strict)
self.cf.zones.settings.ssl.patch(zone_id, data={"value": "full"})
ssl_config["steps"].append({"name": "ssl_mode", "status": "success", "value": "full"})
except Exception as e:
ssl_config["errors"].append({"step": "ssl_mode", "error": str(e)})
try:
# 2. Always Use HTTPS
self.cf.zones.settings.always_use_https.patch(zone_id, data={"value": "on"})
ssl_config["steps"].append({"name": "always_https", "status": "success"})
except Exception as e:
ssl_config["errors"].append({"step": "always_https", "error": str(e)})
try:
# 3. Automatic HTTPS Rewrites
self.cf.zones.settings.automatic_https_rewrites.patch(zone_id, data={"value": "on"})
ssl_config["steps"].append({"name": "auto_https_rewrites", "status": "success"})
except Exception as e:
ssl_config["errors"].append({"step": "auto_https_rewrites", "error": str(e)})
try:
# 4. Minimum TLS Version
self.cf.zones.settings.min_tls_version.patch(zone_id, data={"value": "1.2"})
ssl_config["steps"].append({"name": "min_tls", "status": "success", "value": "1.2"})
except Exception as e:
ssl_config["errors"].append({"step": "min_tls", "error": str(e)})
try:
# 5. TLS 1.3
self.cf.zones.settings.tls_1_3.patch(zone_id, data={"value": "on"})
ssl_config["steps"].append({"name": "tls_1_3", "status": "success"})
except Exception as e:
ssl_config["errors"].append({"step": "tls_1_3", "error": str(e)})
return ssl_config

View File

30
backend/requirements.txt Normal file
View File

@ -0,0 +1,30 @@
# Web Framework
Flask==3.0.0
Flask-CORS==4.0.0
Flask-SQLAlchemy==3.1.1
Flask-Migrate==4.0.5
# Database
psycopg2-binary==2.9.9
SQLAlchemy==2.0.23
# Redis
redis==5.0.1
# Cloudflare
cloudflare==2.19.4
requests==2.31.0
# Utilities
python-dotenv==1.0.0
pydantic==2.5.2
python-dateutil==2.8.2
# Security
cryptography==41.0.7
# Development
pytest==7.4.3
pytest-cov==4.1.0
black==23.12.1
flake8==6.1.0