mirror of
https://github.com/wizarrrr/wizarr.git
synced 2026-07-30 23:07:19 -04:00
164 lines
5.6 KiB
Python
164 lines
5.6 KiB
Python
# Gunicorn configuration with clean logging
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
# Add immediate debug to check if config is loaded
|
|
print("DEBUG: gunicorn.conf.py loaded!")
|
|
|
|
# Make log level configurable for debugging production issues
|
|
# Set GUNICORN_LOG_LEVEL=info or debug for verbose output
|
|
loglevel = os.getenv("GUNICORN_LOG_LEVEL", "warning").lower()
|
|
accesslog = None # Disable access logs for clean output
|
|
errorlog = "-" # Only errors to stderr
|
|
|
|
# Make workers configurable (default 4, but allow override for resource-constrained systems)
|
|
workers = int(os.getenv("GUNICORN_WORKERS", "4"))
|
|
worker_class = "sync"
|
|
|
|
# Worker timeout - kill workers that don't respond within this time
|
|
# Increase from default 30s to 120s to account for slow library scans
|
|
timeout = int(os.getenv("GUNICORN_TIMEOUT", "120"))
|
|
|
|
# Make host and port configurable
|
|
host = os.getenv("HOST", "0.0.0.0")
|
|
port = os.getenv("PORT", "5690")
|
|
bind = f"{host}:{port}"
|
|
|
|
print(
|
|
f"DEBUG: Gunicorn config - workers={workers}, loglevel={loglevel}, timeout={timeout}s, host={host}, port={port}"
|
|
)
|
|
|
|
|
|
def on_starting(server): # noqa: ARG001
|
|
"""Called just before the master process is initialized."""
|
|
print("DEBUG: on_starting() hook called!")
|
|
|
|
|
|
def when_ready(server): # noqa: ARG001
|
|
"""Called after the server is started."""
|
|
try:
|
|
print("DEBUG: when_ready() hook called!")
|
|
|
|
# Set environment to indicate Gunicorn context
|
|
os.environ["SERVER_SOFTWARE"] = "gunicorn"
|
|
|
|
# Only run migrations if we haven't already done so
|
|
if os.getenv("WIZARR_MIGRATIONS_DONE"):
|
|
print("DEBUG: Migrations already done, skipping")
|
|
return
|
|
|
|
print("DEBUG: Setting WIZARR_MIGRATIONS_DONE flag")
|
|
os.environ["WIZARR_MIGRATIONS_DONE"] = "1"
|
|
|
|
# Import here to avoid circular imports
|
|
from app.logging_helpers import AppLogger
|
|
from app.scripts.migrate_libraries import (
|
|
run_library_migration,
|
|
update_server_verified,
|
|
)
|
|
from app.scripts.migrate_media_server import migrate_single_to_multi
|
|
|
|
logger = AppLogger("wizarr.master")
|
|
|
|
# Get the already-created app from run.py
|
|
import run
|
|
|
|
app = run.app
|
|
|
|
# Run master-only migrations and setup
|
|
with app.app_context():
|
|
logger.database_migration("server verification", "verifying media servers")
|
|
update_server_verified(app)
|
|
|
|
logger.database_migration("library migration", "updating library structure")
|
|
run_library_migration(app)
|
|
|
|
logger.database_migration(
|
|
"media server migration", "single to multi-server"
|
|
)
|
|
migrate_single_to_multi(app)
|
|
|
|
# Check Flask-APScheduler status - it handles Gunicorn coordination automatically
|
|
from app.extensions import scheduler
|
|
|
|
print("DEBUG: Checking Flask-APScheduler status...")
|
|
|
|
if scheduler and hasattr(scheduler, "scheduler") and scheduler.scheduler:
|
|
if scheduler.running:
|
|
print("DEBUG: Flask-APScheduler is running")
|
|
dev_mode = os.getenv(
|
|
"WIZARR_ENABLE_SCHEDULER", "false"
|
|
).lower() in (
|
|
"true",
|
|
"1",
|
|
"yes",
|
|
)
|
|
logger.scheduler_status(enabled=True, dev_mode=dev_mode)
|
|
else:
|
|
print("DEBUG: Flask-APScheduler exists but not running")
|
|
logger.warning("Scheduler initialized but not running")
|
|
else:
|
|
print("DEBUG: Flask-APScheduler not available")
|
|
logger.info("Scheduler disabled or not initialized")
|
|
|
|
# Complete the startup sequence
|
|
logger.complete()
|
|
|
|
except Exception as e:
|
|
print(f"DEBUG: Error in when_ready(): {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
def post_worker_init(worker):
|
|
"""Worker process initialization - runs once per worker after spawn."""
|
|
try:
|
|
# Set worker environment
|
|
os.environ["GUNICORN_WORKER_PID"] = str(worker.pid)
|
|
print(f"DEBUG: Worker {worker.pid} initialized successfully")
|
|
|
|
# Suppress Flask app creation logs in workers
|
|
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
|
logging.getLogger("app").setLevel(logging.ERROR)
|
|
except Exception as e:
|
|
print(f"ERROR: Worker {worker.pid} failed to initialize: {e}", file=sys.stderr)
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
raise
|
|
|
|
|
|
def worker_exit(server, worker): # noqa: ARG001
|
|
"""Called when a worker exits (crash or graceful shutdown)."""
|
|
print(f"WARNING: Worker {worker.pid} exited (age: {worker.age}s)")
|
|
|
|
|
|
def worker_abort(worker):
|
|
"""Called when a worker receives SIGABRT (usually due to timeout)."""
|
|
print(
|
|
f"ERROR: Worker {worker.pid} aborted (timeout or critical error)",
|
|
file=sys.stderr,
|
|
)
|
|
import traceback
|
|
|
|
traceback.print_stack()
|
|
|
|
|
|
def on_exit(server): # noqa: ARG001
|
|
"""Called just before the master process exits."""
|
|
print("INFO: Gunicorn master process shutting down")
|
|
|
|
# Gracefully shutdown the APScheduler to prevent "cannot schedule new futures" errors
|
|
try:
|
|
from app.extensions import scheduler
|
|
|
|
if scheduler and hasattr(scheduler, "running") and scheduler.running:
|
|
print("INFO: Shutting down APScheduler...")
|
|
# wait=False to avoid blocking on pending jobs during shutdown
|
|
scheduler.shutdown(wait=False)
|
|
print("INFO: APScheduler shut down successfully")
|
|
except Exception as e:
|
|
print(f"WARNING: Error shutting down APScheduler: {e}")
|