224 lines
6.7 KiB
Python
224 lines
6.7 KiB
Python
|
|
from flask import Flask, request, jsonify
|
|||
|
|
from flask_cors import CORS
|
|||
|
|
from flask_migrate import Migrate
|
|||
|
|
import hashlib
|
|||
|
|
import redis
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
from app.config import Config
|
|||
|
|
from app.models import db, Domain, DNSRecord, CloudflareAccount, User, Customer
|
|||
|
|
from app.services.cloudflare_service import CloudflareService
|
|||
|
|
|
|||
|
|
# Import blueprints
|
|||
|
|
from app.routes.auth import auth_bp
|
|||
|
|
from app.routes.admin import admin_bp
|
|||
|
|
from app.routes.dns import dns_bp
|
|||
|
|
from app.routes.customer import customer_bp
|
|||
|
|
|
|||
|
|
app = Flask(__name__)
|
|||
|
|
app.config.from_object(Config)
|
|||
|
|
|
|||
|
|
# Set ENCRYPTION_KEY environment variable
|
|||
|
|
if Config.ENCRYPTION_KEY:
|
|||
|
|
os.environ['ENCRYPTION_KEY'] = Config.ENCRYPTION_KEY
|
|||
|
|
|
|||
|
|
# Extensions
|
|||
|
|
# CORS - Allow only from argeict.net
|
|||
|
|
CORS(app, resources={
|
|||
|
|
r"/api/*": {
|
|||
|
|
"origins": ["https://argeict.net"],
|
|||
|
|
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|||
|
|
"allow_headers": ["Content-Type", "Authorization"]
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
db.init_app(app)
|
|||
|
|
migrate = Migrate(app, db)
|
|||
|
|
|
|||
|
|
# Register blueprints
|
|||
|
|
app.register_blueprint(auth_bp)
|
|||
|
|
app.register_blueprint(admin_bp)
|
|||
|
|
app.register_blueprint(dns_bp)
|
|||
|
|
app.register_blueprint(customer_bp)
|
|||
|
|
|
|||
|
|
# 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())
|
|||
|
|
|
|||
|
|
|
|||
|
|
@app.route('/webhook/deploy', methods=['POST'])
|
|||
|
|
def webhook_deploy():
|
|||
|
|
"""Gitea webhook for auto-deployment"""
|
|||
|
|
import subprocess
|
|||
|
|
import os
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
# Get webhook data
|
|||
|
|
data = request.json or {}
|
|||
|
|
|
|||
|
|
# Log webhook event
|
|||
|
|
repo_name = data.get('repository', {}).get('name', 'unknown')
|
|||
|
|
pusher = data.get('pusher', {}).get('username', 'unknown')
|
|||
|
|
|
|||
|
|
print(f"📥 Webhook received from {repo_name} by {pusher} at {datetime.now()}")
|
|||
|
|
|
|||
|
|
# Trigger deployment script in background
|
|||
|
|
try:
|
|||
|
|
# Run deployment script asynchronously
|
|||
|
|
process = subprocess.Popen(
|
|||
|
|
['/opt/hosting-platform/deploy-local.sh'],
|
|||
|
|
stdout=subprocess.PIPE,
|
|||
|
|
stderr=subprocess.PIPE,
|
|||
|
|
text=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Don't wait for completion, return immediately
|
|||
|
|
return jsonify({
|
|||
|
|
"status": "success",
|
|||
|
|
"message": "Deployment triggered successfully",
|
|||
|
|
"repository": repo_name,
|
|||
|
|
"pusher": pusher,
|
|||
|
|
"timestamp": datetime.now().isoformat(),
|
|||
|
|
"note": "Check /var/log/auto-deploy.log for deployment progress"
|
|||
|
|
}), 202 # 202 Accepted
|
|||
|
|
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
return jsonify({
|
|||
|
|
"status": "error",
|
|||
|
|
"message": "Deployment script not found at /opt/hosting-platform/deploy-local.sh"
|
|||
|
|
}), 500
|
|||
|
|
except Exception as e:
|
|||
|
|
return jsonify({
|
|||
|
|
"status": "error",
|
|||
|
|
"message": f"Failed to trigger deployment: {str(e)}"
|
|||
|
|
}), 500
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
with app.app_context():
|
|||
|
|
db.create_all()
|
|||
|
|
app.run(host=Config.API_HOST, port=Config.API_PORT, debug=True)
|
|||
|
|
|