Files
ServerKit/backend/app/__init__.py
T
Juan Denis b7fab1152b Add invitations, email models, admin APIs
Introduce team invitation support and a revamped email subsystem plus admin features. Adds Invitation model and API endpoints (create, list, revoke, resend, validate) and integrates invitation validation into user registration. Replaces old email account model with richer email models (EmailDomain, EmailAccount, EmailAlias, EmailForwardingRule, DNSProviderConfig) and updates models __init__. Refactors email API to manage domains, accounts, aliases, forwarding, DNS providers, spamassassin, Roundcube/Postfix services and queue/log endpoints. Adds several backend services (invitation, DNS provider, DKIM, Dovecot, Postfix, Roundcube, SpamAssassin, permission service) and a DB migration for permissions/invitations. Enhances admin API with permission management and activity dashboard endpoints, extends audit log actions, and adds a permission_required RBAC decorator. Includes frontend additions for settings/invitations UI and miscellaneous config/service updates and tests.
2026-03-04 02:34:49 -05:00

386 lines
14 KiB
Python

import os
from flask import Flask, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_migrate import Migrate
from config import config
db = SQLAlchemy()
jwt = JWTManager()
migrate = Migrate()
limiter = Limiter(key_func=get_remote_address, default_limits=["100 per minute"])
# Note: key_func is updated to get_rate_limit_key after app init
socketio = None
# Path to frontend dist folder (relative to backend folder)
FRONTEND_DIST = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'dist')
def create_app(config_name=None):
global socketio
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'development')
# Configure Flask to serve static files from frontend dist
app = Flask(
__name__,
static_folder=FRONTEND_DIST,
static_url_path=''
)
app.config.from_object(config[config_name])
# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
limiter.init_app(app)
CORS(
app,
origins=app.config['CORS_ORIGINS'],
supports_credentials=True,
allow_headers=['Content-Type', 'Authorization', 'X-Requested-With', 'X-API-Key'],
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
)
# Register security headers middleware
from app.middleware.security import register_security_headers
register_security_headers(app)
# Register API key authentication middleware
from app.middleware.api_key_auth import register_api_key_auth
register_api_key_auth(app)
# Register API analytics middleware
from app.middleware.api_analytics import register_api_analytics
register_api_analytics(app)
# Update rate limiter with custom key function
from app.middleware.rate_limit import get_rate_limit_key, register_rate_limit_headers
limiter._key_func = get_rate_limit_key
register_rate_limit_headers(app)
# Initialize SocketIO
from app.sockets import init_socketio
socketio = init_socketio(app)
# Initialize Agent Gateway
from app.agent_gateway import init_agent_gateway
init_agent_gateway(socketio)
# Register blueprints - Auth
from app.api.auth import auth_bp
app.register_blueprint(auth_bp, url_prefix='/api/v1/auth')
# Register blueprints - Core
from app.api.apps import apps_bp
from app.api.domains import domains_bp
from app.api.private_urls import private_urls_bp
app.register_blueprint(apps_bp, url_prefix='/api/v1/apps')
app.register_blueprint(domains_bp, url_prefix='/api/v1/domains')
app.register_blueprint(private_urls_bp, url_prefix='/api/v1/apps')
# Register blueprints - System
from app.api.system import system_bp
from app.api.processes import processes_bp
from app.api.logs import logs_bp
app.register_blueprint(system_bp, url_prefix='/api/v1/system')
app.register_blueprint(processes_bp, url_prefix='/api/v1/processes')
app.register_blueprint(logs_bp, url_prefix='/api/v1/logs')
# Register blueprints - Infrastructure
from app.api.nginx import nginx_bp
from app.api.ssl import ssl_bp
app.register_blueprint(nginx_bp, url_prefix='/api/v1/nginx')
app.register_blueprint(ssl_bp, url_prefix='/api/v1/ssl')
# Register blueprints - PHP & WordPress
from app.api.php import php_bp
from app.api.wordpress import wordpress_bp
from app.api.wordpress_sites import wordpress_sites_bp
from app.api.environment_pipeline import environment_pipeline_bp
app.register_blueprint(php_bp, url_prefix='/api/v1/php')
app.register_blueprint(wordpress_bp, url_prefix='/api/v1/wordpress')
app.register_blueprint(wordpress_sites_bp, url_prefix='/api/v1/wordpress')
app.register_blueprint(environment_pipeline_bp, url_prefix='/api/v1/wordpress/projects')
# Register blueprints - Python
from app.api.python import python_bp
app.register_blueprint(python_bp, url_prefix='/api/v1/python')
# Register blueprints - Docker
from app.api.docker import docker_bp
app.register_blueprint(docker_bp, url_prefix='/api/v1/docker')
# Register blueprints - Databases
from app.api.databases import databases_bp
app.register_blueprint(databases_bp, url_prefix='/api/v1/databases')
# Register blueprints - Monitoring & Alerts
from app.api.monitoring import monitoring_bp
app.register_blueprint(monitoring_bp, url_prefix='/api/v1/monitoring')
# Register blueprints - Notifications
from app.api.notifications import notifications_bp
app.register_blueprint(notifications_bp, url_prefix='/api/v1/notifications')
# Register blueprints - Backups
from app.api.backups import backups_bp
app.register_blueprint(backups_bp, url_prefix='/api/v1/backups')
# Register blueprints - Git Deployment
from app.api.deploy import deploy_bp
app.register_blueprint(deploy_bp, url_prefix='/api/v1/deploy')
# Register blueprints - Builds & Deployments
from app.api.builds import builds_bp
app.register_blueprint(builds_bp, url_prefix='/api/v1/builds')
# Register blueprints - Templates
from app.api.templates import templates_bp
app.register_blueprint(templates_bp, url_prefix='/api/v1/templates')
# Register blueprints - File Manager
from app.api.files import files_bp
app.register_blueprint(files_bp, url_prefix='/api/v1/files')
# Register blueprints - FTP Server
from app.api.ftp import ftp_bp
app.register_blueprint(ftp_bp, url_prefix='/api/v1/ftp')
# Register blueprints - Firewall
from app.api.firewall import firewall_bp
app.register_blueprint(firewall_bp, url_prefix='/api/v1/firewall')
# Register blueprints - Git Server
from app.api.git import git_bp
app.register_blueprint(git_bp, url_prefix='/api/v1/git')
# Register blueprints - Security (ClamAV, File Integrity, etc.)
from app.api.security import security_bp
app.register_blueprint(security_bp, url_prefix='/api/v1/security')
# Register blueprints - Cron Jobs
from app.api.cron import cron_bp
app.register_blueprint(cron_bp, url_prefix='/api/v1/cron')
# Register blueprints - Email Server
from app.api.email import email_bp
app.register_blueprint(email_bp, url_prefix='/api/v1/email')
# Register blueprints - Uptime Tracking
from app.api.uptime import uptime_bp
app.register_blueprint(uptime_bp, url_prefix='/api/v1/uptime')
# Register blueprints - Environment Variables
from app.api.env_vars import env_vars_bp
app.register_blueprint(env_vars_bp, url_prefix='/api/v1/apps')
# Register blueprints - Two-Factor Authentication
from app.api.two_factor import two_factor_bp
app.register_blueprint(two_factor_bp, url_prefix='/api/v1/auth/2fa')
# Register blueprints - SSO / OAuth
from app.api.sso import sso_bp
app.register_blueprint(sso_bp, url_prefix='/api/v1/sso')
# Register blueprints - Database Migrations
from app.api.migrations import migrations_bp
app.register_blueprint(migrations_bp, url_prefix='/api/v1/migrations')
# Register blueprints - API Enhancements
from app.api.api_keys import api_keys_bp
from app.api.api_analytics import api_analytics_bp
from app.api.event_subscriptions import event_subscriptions_bp
from app.api.docs import docs_bp
app.register_blueprint(api_keys_bp, url_prefix='/api/v1/api-keys')
app.register_blueprint(api_analytics_bp, url_prefix='/api/v1/api-analytics')
app.register_blueprint(event_subscriptions_bp, url_prefix='/api/v1/event-subscriptions')
app.register_blueprint(docs_bp, url_prefix='/api/v1/docs')
# Register blueprints - Admin (User Management, Settings, Audit Logs)
from app.api.admin import admin_bp
app.register_blueprint(admin_bp, url_prefix='/api/v1/admin')
# Register blueprints - Invitations
from app.api.invitations import invitations_bp
app.register_blueprint(invitations_bp, url_prefix='/api/v1/admin/invitations')
# Register blueprints - Historical Metrics
from app.api.metrics import metrics_bp
app.register_blueprint(metrics_bp, url_prefix='/api/v1/metrics')
# Register blueprints - Workflows
from app.api.workflows import workflows_bp
app.register_blueprint(workflows_bp, url_prefix='/api/v1/workflows')
# Register blueprints - Servers (Multi-server management)
from app.api.servers import servers_bp
app.register_blueprint(servers_bp, url_prefix='/api/v1/servers')
# Handle database migrations (Alembic)
with app.app_context():
from app.services.migration_service import MigrationService
MigrationService.check_and_prepare(app)
# Initialize default settings and migrate legacy roles
from app.services.settings_service import SettingsService
SettingsService.initialize_defaults()
SettingsService.migrate_legacy_roles()
# Start metrics history collection in background
from app.services.metrics_history_service import MetricsHistoryService
if not MetricsHistoryService.is_running():
MetricsHistoryService.start_collection(app)
# Start auto-sync scheduler for WordPress environments
_start_auto_sync_scheduler(app)
# Start API analytics flush thread
from app.middleware.api_analytics import start_analytics_flush_thread
start_analytics_flush_thread(app)
# Start hourly analytics aggregation and event retry threads
_start_api_background_threads(app)
# Serve frontend for root path
@app.route('/')
def serve_index():
index = os.path.join(app.static_folder, 'index.html') if app.static_folder else None
if index and os.path.isfile(index):
return send_from_directory(app.static_folder, 'index.html')
return {'message': 'ServerKit API is running', 'docs': '/api/v1/'}, 200
# Catch-all route for SPA - must be after all other routes
@app.errorhandler(404)
def not_found(e):
from flask import request
if request.path.startswith('/api/'):
return {'error': 'Not found'}, 404
# Serve SPA index.html if it exists, otherwise JSON 404
index = os.path.join(app.static_folder, 'index.html') if app.static_folder else None
if index and os.path.isfile(index):
return send_from_directory(app.static_folder, 'index.html')
return {'error': 'Not found'}, 404
return app
def get_socketio():
"""Get the SocketIO instance."""
return socketio
_auto_sync_thread = None
def _start_auto_sync_scheduler(app):
"""Start a background thread that checks for auto-sync schedules."""
global _auto_sync_thread
if _auto_sync_thread is not None:
return
import threading
import time
import logging
logger = logging.getLogger(__name__)
def auto_sync_loop():
while True:
try:
time.sleep(60) # Check every 60 seconds
with app.app_context():
_check_auto_sync_schedules(logger)
except Exception as e:
logger.error(f'Auto-sync scheduler error: {e}')
_auto_sync_thread = threading.Thread(
target=auto_sync_loop,
daemon=True,
name='auto-sync-scheduler'
)
_auto_sync_thread.start()
def _check_auto_sync_schedules(logger):
"""Check all auto-sync enabled sites and run syncs that are due."""
from app.models.wordpress_site import WordPressSite
from datetime import datetime
sites = WordPressSite.query.filter_by(auto_sync_enabled=True).all()
if not sites:
return
try:
from croniter import croniter
except ImportError:
logger.debug('croniter not installed, skipping auto-sync check')
return
now = datetime.utcnow()
for site in sites:
if not site.auto_sync_schedule:
continue
try:
if not croniter.is_valid(site.auto_sync_schedule):
continue
cron = croniter(site.auto_sync_schedule, now)
prev_run = cron.get_prev(datetime)
# Check if a run was due in the last 90 seconds (to account for check interval)
seconds_since_due = (now - prev_run).total_seconds()
if seconds_since_due <= 90:
logger.info(f'Auto-sync triggered for site {site.id} ({site.name})')
from app.services.environment_pipeline_service import EnvironmentPipelineService
EnvironmentPipelineService.sync_from_production(
env_site_id=site.id,
sync_type='full',
user_id=None
)
except Exception as e:
logger.error(f'Auto-sync check failed for site {site.id}: {e}')
_api_bg_thread = None
def _start_api_background_threads(app):
"""Start background threads for API analytics aggregation and event delivery retry."""
global _api_bg_thread
if _api_bg_thread is not None:
return
import threading
import time
import logging
logger = logging.getLogger(__name__)
def api_bg_loop():
while True:
try:
time.sleep(3600) # Run hourly
with app.app_context():
from app.services.api_analytics_service import ApiAnalyticsService
ApiAnalyticsService.aggregate_hourly()
from app.services.event_service import EventService
EventService.retry_failed()
except Exception as e:
logger.error(f'API background thread error: {e}')
_api_bg_thread = threading.Thread(
target=api_bg_loop,
daemon=True,
name='api-background'
)
_api_bg_thread.start()