"""Gunicorn configuration file for ESPHome Gitea Sync Service""" import logging from threading import Thread # Gunicorn configuration bind = "0.0.0.0:5000" workers = 2 timeout = 600 loglevel = "info" logger = logging.getLogger(__name__) # Track which worker has the file watcher (using a file-based flag) import os import tempfile WATCHER_LOCK_FILE = os.path.join(tempfile.gettempdir(), 'esphome_watcher.lock') def on_starting(server): """ Called just before the master process is initialized. Perfect for one-time setup like git initialization. """ logger.info("Gunicorn master process starting - initializing git repository") # Clean up any stale lock file from previous runs if os.path.exists(WATCHER_LOCK_FILE): try: os.remove(WATCHER_LOCK_FILE) logger.info("Removed stale watcher lock file") except Exception as e: logger.warning(f"Could not remove stale lock file: {e}") # Import here to avoid circular imports from app import initialize_git_repo try: initialize_git_repo() logger.info("Git repository initialized successfully") except Exception as e: logger.error(f"Failed to initialize git repository: {e}") def post_fork(server, worker): """ Called after a worker has been forked. Start the file watcher only in the first worker to avoid duplicates. """ # Use a file-based lock to ensure only one worker starts the watcher # This works across process boundaries unlike global variables try: # Try to create the lock file (exclusive creation) fd = os.open(WATCHER_LOCK_FILE, os.O_CREAT | os.O_EXCL | os.O_WRONLY) os.write(fd, str(worker.pid).encode()) os.close(fd) # This worker won the race, start the file watcher logger.info(f"Starting file watcher in worker {worker.pid}") # Import here to avoid circular imports from app import start_file_watcher try: # Start file watcher in a daemon thread watcher_thread = Thread(target=start_file_watcher, daemon=True) watcher_thread.start() logger.info("File watcher started successfully") except Exception as e: logger.error(f"Failed to start file watcher: {e}") # Clean up lock file on failure try: os.remove(WATCHER_LOCK_FILE) except: pass except FileExistsError: # Another worker already started the watcher logger.info(f"Worker {worker.pid} started (file watcher already running in another worker)") except Exception as e: logger.error(f"Error in post_fork for worker {worker.pid}: {e}")