mirror of
https://github.com/plexguide/Huntarr-Sonarr.git
synced 2025-12-16 20:04:16 -06:00
- Implemented health check endpoint for Docker and orchestration systems. - Added graceful shutdown configuration in Docker Compose and application code. - Enhanced shutdown handling in main application and background tasks for improved diagnostics. - Updated Dockerfile to include health check command. - Introduced readiness check endpoint for Kubernetes-style orchestration.
558 lines
24 KiB
Python
558 lines
24 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Main entry point for Huntarr
|
|
Starts both the web server and the background processing tasks.
|
|
"""
|
|
|
|
import os
|
|
import threading
|
|
import sys
|
|
import signal
|
|
import logging # Use standard logging for initial setup
|
|
import atexit
|
|
import time
|
|
import time
|
|
|
|
# Import path configuration early to set up environment
|
|
try:
|
|
from src.primary.utils import config_paths
|
|
print(f"Using config directory: {config_paths.CONFIG_DIR}")
|
|
except Exception as e:
|
|
print(f"Warning: Failed to initialize config paths: {str(e)}")
|
|
# Continue anyway - we'll handle this later
|
|
|
|
# Ensure the 'src' directory is in the Python path
|
|
# This allows importing modules from 'src.primary' etc.
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')))
|
|
|
|
# --- Early Logging Setup (Before importing app components) ---
|
|
# Basic logging to capture early errors during import or setup
|
|
log_level = logging.DEBUG if os.environ.get('DEBUG', 'false').lower() == 'true' else logging.INFO
|
|
|
|
# Create a custom formatter that uses local time
|
|
class LocalTimeFormatter(logging.Formatter):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.converter = time.localtime # Use local time instead of UTC
|
|
|
|
# Disable basic logging to prevent duplicates - we use custom loggers
|
|
# logging.basicConfig(level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
|
|
# Apply local time converter to all existing handlers
|
|
for handler in logging.root.handlers:
|
|
if hasattr(handler, 'formatter') and handler.formatter:
|
|
handler.formatter.converter = time.localtime
|
|
|
|
root_logger = logging.getLogger("HuntarrRoot") # Specific logger for this entry point
|
|
root_logger.info("--- Huntarr Main Process Starting ---")
|
|
root_logger.info(f"Python sys.path: {sys.path}")
|
|
|
|
# Check for Windows service commands
|
|
if sys.platform == 'win32' and len(sys.argv) > 1:
|
|
if sys.argv[1] == '--install-service':
|
|
try:
|
|
from src.primary.windows_service import install_service
|
|
success = install_service()
|
|
sys.exit(0 if success else 1)
|
|
except ImportError:
|
|
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
root_logger.exception(f"Error installing Windows service: {e}")
|
|
sys.exit(1)
|
|
elif sys.argv[1] == '--remove-service':
|
|
try:
|
|
from src.primary.windows_service import remove_service
|
|
success = remove_service()
|
|
sys.exit(0 if success else 1)
|
|
except ImportError:
|
|
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
root_logger.exception(f"Error removing Windows service: {e}")
|
|
sys.exit(1)
|
|
elif sys.argv[1] in ['--start', '--stop', '--restart', '--debug', '--update']:
|
|
try:
|
|
import win32serviceutil
|
|
service_name = "Huntarr"
|
|
if sys.argv[1] == '--start':
|
|
win32serviceutil.StartService(service_name)
|
|
print(f"Started {service_name} service")
|
|
elif sys.argv[1] == '--stop':
|
|
win32serviceutil.StopService(service_name)
|
|
print(f"Stopped {service_name} service")
|
|
elif sys.argv[1] == '--restart':
|
|
win32serviceutil.RestartService(service_name)
|
|
print(f"Restarted {service_name} service")
|
|
elif sys.argv[1] == '--debug':
|
|
# Run the service in debug mode directly
|
|
from src.primary.windows_service import HuntarrService
|
|
win32serviceutil.HandleCommandLine(HuntarrService)
|
|
elif sys.argv[1] == '--update':
|
|
# Update the service
|
|
win32serviceutil.StopService(service_name)
|
|
from src.primary.windows_service import install_service
|
|
install_service()
|
|
win32serviceutil.StartService(service_name)
|
|
print(f"Updated {service_name} service")
|
|
sys.exit(0)
|
|
except ImportError:
|
|
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
root_logger.exception(f"Error managing Windows service: {e}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
# Import the Flask app instance
|
|
from primary.web_server import app
|
|
# Import the background task starter function and shutdown helpers from the renamed file
|
|
from primary.background import start_huntarr, stop_event, shutdown_threads
|
|
# Configure logging first
|
|
import logging
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
from primary.utils.logger import setup_main_logger, get_logger
|
|
from primary.utils.clean_logger import setup_clean_logging
|
|
|
|
# Initialize main logger
|
|
huntarr_logger = setup_main_logger()
|
|
|
|
# Initialize timezone from TZ environment variable
|
|
try:
|
|
from primary.settings_manager import initialize_timezone_from_env
|
|
initialize_timezone_from_env()
|
|
huntarr_logger.info("Timezone initialization completed.")
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Failed to initialize timezone from environment: {e}")
|
|
|
|
|
|
|
|
# Initialize clean logging for frontend consumption
|
|
setup_clean_logging()
|
|
huntarr_logger.info("Clean logging system initialized for frontend consumption.")
|
|
|
|
huntarr_logger.info("Successfully imported application components.")
|
|
# Main function startup message removed to reduce log spam
|
|
except ImportError as e:
|
|
root_logger.critical(f"Fatal Error: Failed to import application components: {e}", exc_info=True)
|
|
root_logger.critical("Please ensure the application structure is correct, dependencies are installed (`pip install -r requirements.txt`), and the script is run from the project root.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
root_logger.critical(f"Fatal Error: An unexpected error occurred during initial imports: {e}", exc_info=True)
|
|
sys.exit(1)
|
|
|
|
# Global variables for server management
|
|
waitress_server = None
|
|
shutdown_requested = threading.Event()
|
|
|
|
# Global shutdown flag for health checks
|
|
_global_shutdown_flag = False
|
|
|
|
def is_shutting_down():
|
|
"""Check if the application is shutting down"""
|
|
global _global_shutdown_flag
|
|
return _global_shutdown_flag or shutdown_requested.is_set() or stop_event.is_set()
|
|
|
|
def refresh_sponsors_on_startup():
|
|
"""Refresh sponsors database from manifest.json on startup"""
|
|
import os
|
|
import json
|
|
|
|
try:
|
|
# Get database instance
|
|
from src.primary.utils.database import get_database
|
|
db = get_database()
|
|
|
|
# Path to manifest.json
|
|
manifest_path = os.path.join(os.path.dirname(__file__), 'manifest.json')
|
|
|
|
if os.path.exists(manifest_path):
|
|
with open(manifest_path, 'r') as f:
|
|
manifest_data = json.load(f)
|
|
|
|
sponsors_list = manifest_data.get('sponsors', [])
|
|
if sponsors_list:
|
|
# Clear existing sponsors and save new ones
|
|
db.save_sponsors(sponsors_list)
|
|
huntarr_logger.debug(f"Refreshed {len(sponsors_list)} sponsors from manifest.json")
|
|
else:
|
|
huntarr_logger.warning("No sponsors found in manifest.json")
|
|
else:
|
|
huntarr_logger.warning(f"manifest.json not found at {manifest_path}")
|
|
|
|
except Exception as e:
|
|
huntarr_logger.error(f"Error refreshing sponsors on startup: {e}")
|
|
raise
|
|
|
|
def load_version_to_database():
|
|
"""Load current version from version.txt into database on startup"""
|
|
import os
|
|
|
|
try:
|
|
# Get database instance
|
|
from src.primary.utils.database import get_database
|
|
db = get_database()
|
|
|
|
# Path to version.txt
|
|
version_path = os.path.join(os.path.dirname(__file__), 'version.txt')
|
|
|
|
if os.path.exists(version_path):
|
|
with open(version_path, 'r') as f:
|
|
version = f.read().strip()
|
|
|
|
if version:
|
|
# Store version in database
|
|
db.set_version(version)
|
|
huntarr_logger.info(f"Version {version} loaded into database")
|
|
else:
|
|
huntarr_logger.warning("version.txt is empty")
|
|
else:
|
|
huntarr_logger.warning(f"version.txt not found at {version_path}")
|
|
|
|
except Exception as e:
|
|
huntarr_logger.error(f"Error loading version to database: {e}")
|
|
# Don't raise - this is not critical enough to stop startup
|
|
|
|
def run_background_tasks():
|
|
"""Runs the Huntarr background processing."""
|
|
bg_logger = get_logger("HuntarrBackground") # Use app's logger
|
|
try:
|
|
bg_logger.info("Starting Huntarr background tasks...")
|
|
start_huntarr() # This function contains the main loop and shutdown logic
|
|
except Exception as e:
|
|
bg_logger.exception(f"Critical error in Huntarr background tasks: {e}")
|
|
finally:
|
|
bg_logger.info("Huntarr background tasks stopped.")
|
|
|
|
def run_web_server():
|
|
"""Runs the Flask web server using Waitress in production."""
|
|
global waitress_server
|
|
web_logger = get_logger("WebServer") # Use app's logger
|
|
debug_mode = os.environ.get('DEBUG', 'false').lower() == 'true'
|
|
host = os.environ.get('FLASK_HOST', '0.0.0.0')
|
|
port = int(os.environ.get('HUNTARR_PORT', os.environ.get('PORT', 9705))) # Check HUNTARR_PORT first, then PORT, then default
|
|
|
|
web_logger.info(f"Starting web server on {host}:{port} (Debug: {debug_mode})...")
|
|
|
|
# Log the current authentication mode once at startup
|
|
try:
|
|
import sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
from primary.settings_manager import load_settings
|
|
|
|
settings = load_settings("general")
|
|
local_access_bypass = settings.get("local_access_bypass", False)
|
|
proxy_auth_bypass = settings.get("proxy_auth_bypass", False)
|
|
|
|
if proxy_auth_bypass:
|
|
web_logger.info("🔓 Authentication Mode: NO LOGIN MODE (Proxy authentication bypass enabled)")
|
|
elif local_access_bypass:
|
|
web_logger.info("🏠 Authentication Mode: LOCAL ACCESS BYPASS (Local network authentication bypass enabled)")
|
|
else:
|
|
web_logger.info("🔐 Authentication Mode: STANDARD (Full authentication required)")
|
|
except Exception as e:
|
|
web_logger.warning(f"Could not determine authentication mode at startup: {e}")
|
|
|
|
if debug_mode:
|
|
# Use Flask's development server for debugging (less efficient, auto-reloads)
|
|
# Note: use_reloader=True can cause issues with threads starting twice.
|
|
web_logger.warning("Running in DEBUG mode with Flask development server.")
|
|
try:
|
|
app.run(host=host, port=port, debug=True, use_reloader=False)
|
|
except Exception as e:
|
|
web_logger.exception(f"Flask development server failed: {e}")
|
|
# Signal background thread to stop if server fails critically
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
else:
|
|
# Use Waitress for production with proper signal handling
|
|
try:
|
|
from waitress import serve
|
|
from waitress.server import create_server
|
|
import time
|
|
web_logger.info("Running with Waitress production server.")
|
|
|
|
# Create the server instance so we can shut it down gracefully
|
|
waitress_server = create_server(app, host=host, port=port, threads=8)
|
|
|
|
web_logger.info("Waitress server starting...")
|
|
|
|
# Start the server in a separate thread
|
|
server_thread = threading.Thread(target=waitress_server.run, daemon=True)
|
|
server_thread.start()
|
|
|
|
# Monitor for shutdown signal in the main thread
|
|
while not shutdown_requested.is_set() and not stop_event.is_set():
|
|
try:
|
|
# Check both shutdown events
|
|
if shutdown_requested.wait(timeout=0.5) or stop_event.wait(timeout=0.5):
|
|
break
|
|
except KeyboardInterrupt:
|
|
break
|
|
|
|
# Shutdown sequence
|
|
web_logger.info("Shutdown signal received. Stopping Waitress server...")
|
|
try:
|
|
waitress_server.close()
|
|
web_logger.info("Waitress server close() called.")
|
|
|
|
# Wait for server thread to finish
|
|
server_thread.join(timeout=3.0)
|
|
if server_thread.is_alive():
|
|
web_logger.warning("Server thread did not stop within timeout.")
|
|
else:
|
|
web_logger.info("Server thread stopped successfully.")
|
|
|
|
except Exception as e:
|
|
web_logger.exception(f"Error during Waitress server shutdown: {e}")
|
|
|
|
web_logger.info("Waitress server has stopped.")
|
|
|
|
except ImportError:
|
|
web_logger.error("Waitress not found. Falling back to Flask development server (NOT recommended for production).")
|
|
web_logger.error("Install waitress ('pip install waitress') for production use.")
|
|
try:
|
|
app.run(host=host, port=port, debug=False, use_reloader=False)
|
|
except Exception as e:
|
|
web_logger.exception(f"Flask development server (fallback) failed: {e}")
|
|
# Signal background thread to stop if server fails critically
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
except Exception as e:
|
|
web_logger.exception(f"Waitress server failed: {e}")
|
|
# Signal background thread to stop if server fails critically
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
|
|
def main_shutdown_handler(signum, frame):
|
|
"""Gracefully shut down the application."""
|
|
global _global_shutdown_flag
|
|
_global_shutdown_flag = True # Set global shutdown flag immediately
|
|
|
|
signal_name = "SIGINT" if signum == signal.SIGINT else "SIGTERM" if signum == signal.SIGTERM else f"Signal {signum}"
|
|
huntarr_logger.info(f"Received {signal_name}. Initiating graceful shutdown...")
|
|
|
|
# Set a reasonable timeout for shutdown operations
|
|
shutdown_start_time = time.time()
|
|
shutdown_timeout = 30 # 30 seconds total shutdown timeout
|
|
|
|
# Immediate database checkpoint to prevent corruption
|
|
try:
|
|
from primary.utils.database import get_database, get_logs_database
|
|
|
|
huntarr_logger.info("Performing emergency database checkpoint...")
|
|
|
|
# Emergency checkpoint for main database
|
|
try:
|
|
main_db = get_database()
|
|
with main_db.get_connection() as conn:
|
|
conn.execute("PRAGMA wal_checkpoint(RESTART)")
|
|
huntarr_logger.info("Main database emergency checkpoint completed")
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Main database emergency checkpoint failed: {e}")
|
|
|
|
# Emergency checkpoint for logs database
|
|
try:
|
|
logs_db = get_logs_database()
|
|
with logs_db.get_logs_connection() as conn:
|
|
conn.execute("PRAGMA wal_checkpoint(RESTART)")
|
|
huntarr_logger.info("Logs database emergency checkpoint completed")
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Logs database emergency checkpoint failed: {e}")
|
|
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Emergency database checkpoint failed: {e}")
|
|
|
|
# Set both shutdown events
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
if not shutdown_requested.is_set():
|
|
shutdown_requested.set()
|
|
|
|
# Also shutdown the Waitress server directly if it exists
|
|
global waitress_server
|
|
if waitress_server:
|
|
try:
|
|
huntarr_logger.info("Signaling Waitress server to shutdown...")
|
|
waitress_server.close()
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Error closing Waitress server: {e}")
|
|
|
|
# Force exit if shutdown takes too long (Docker container update scenario)
|
|
elapsed_time = time.time() - shutdown_start_time
|
|
if elapsed_time > shutdown_timeout:
|
|
huntarr_logger.warning(f"Shutdown timeout exceeded ({shutdown_timeout}s). Forcing exit with code 0.")
|
|
os._exit(0) # Clean exit for Docker updates
|
|
|
|
def cleanup_handler():
|
|
"""Cleanup function called at exit"""
|
|
cleanup_start_time = time.time()
|
|
huntarr_logger.info("Exit cleanup handler called")
|
|
|
|
# Shutdown databases gracefully with timeout
|
|
try:
|
|
from primary.utils.database import get_database, get_logs_database
|
|
|
|
# Close main database connections
|
|
main_db = get_database()
|
|
if hasattr(main_db, '_database_instance') and main_db._database_instance is not None:
|
|
huntarr_logger.info("Closing main database connections...")
|
|
# Force close any open connections
|
|
try:
|
|
with main_db.get_connection() as conn:
|
|
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)") # Flush WAL to main database
|
|
# Skip VACUUM for faster shutdown during updates
|
|
huntarr_logger.debug("Main database WAL checkpoint completed")
|
|
except Exception as db_error:
|
|
huntarr_logger.warning(f"Error during main database cleanup: {db_error}")
|
|
|
|
# Close logs database connections
|
|
logs_db = get_logs_database()
|
|
if hasattr(logs_db, '_logs_database_instance') and logs_db._logs_database_instance is not None:
|
|
huntarr_logger.info("Closing logs database connections...")
|
|
try:
|
|
with logs_db.get_logs_connection() as conn:
|
|
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)") # Flush WAL to logs database
|
|
# Skip VACUUM for faster shutdown during updates
|
|
huntarr_logger.debug("Logs database WAL checkpoint completed")
|
|
except Exception as logs_error:
|
|
huntarr_logger.warning(f"Error during logs database cleanup: {logs_error}")
|
|
|
|
huntarr_logger.info("Database shutdown completed")
|
|
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Error during database shutdown: {e}")
|
|
|
|
# Ensure stop events are set
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
if not shutdown_requested.is_set():
|
|
shutdown_requested.set()
|
|
|
|
# Log cleanup timing for Docker update diagnostics
|
|
cleanup_duration = time.time() - cleanup_start_time
|
|
huntarr_logger.info(f"Cleanup completed in {cleanup_duration:.2f} seconds")
|
|
|
|
def main():
|
|
"""Main entry point function for Huntarr application.
|
|
This function is called by app_launcher.py in the packaged ARM application.
|
|
"""
|
|
# Register signal handlers for graceful shutdown in the main process
|
|
signal.signal(signal.SIGINT, main_shutdown_handler)
|
|
signal.signal(signal.SIGTERM, main_shutdown_handler)
|
|
|
|
# Register cleanup handler
|
|
atexit.register(cleanup_handler)
|
|
|
|
# Initialize databases with default configurations
|
|
try:
|
|
from primary.settings_manager import initialize_database
|
|
initialize_database()
|
|
huntarr_logger.info("Main database initialization completed successfully")
|
|
|
|
# Initialize base URL from BASE_URL environment variable early
|
|
# This needs to happen before web server initialization
|
|
try:
|
|
from primary.settings_manager import initialize_base_url_from_env
|
|
initialize_base_url_from_env()
|
|
huntarr_logger.info("Base URL initialization completed.")
|
|
|
|
# Reconfigure the web server with the updated base URL
|
|
from primary.web_server import reconfigure_base_url
|
|
reconfigure_base_url()
|
|
huntarr_logger.info("Web server reconfigured with updated base URL.")
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Failed to initialize base URL from environment: {e}")
|
|
|
|
# Initialize database logging system (now uses main huntarr.db)
|
|
try:
|
|
from primary.utils.database import get_logs_database, schedule_log_cleanup
|
|
logs_db = get_logs_database()
|
|
schedule_log_cleanup()
|
|
huntarr_logger.info("Database logging system initialized with scheduled cleanup.")
|
|
except Exception as e:
|
|
huntarr_logger.warning(f"Failed to initialize database logging: {e}")
|
|
|
|
# Load version from version.txt into database on startup
|
|
try:
|
|
load_version_to_database()
|
|
except Exception as version_error:
|
|
huntarr_logger.warning(f"Failed to load version to database: {version_error}")
|
|
|
|
# Refresh sponsors from manifest.json on startup
|
|
try:
|
|
refresh_sponsors_on_startup()
|
|
huntarr_logger.info("Sponsors database refreshed from manifest.json")
|
|
except Exception as sponsor_error:
|
|
huntarr_logger.warning(f"Failed to refresh sponsors on startup: {sponsor_error}")
|
|
|
|
except Exception as e:
|
|
huntarr_logger.error(f"Failed to initialize databases: {e}")
|
|
huntarr_logger.error("Application may not function correctly without database")
|
|
# Continue anyway - the app might still work with defaults
|
|
|
|
background_thread = None
|
|
try:
|
|
# Start background tasks in a daemon thread
|
|
# Daemon threads exit automatically if the main thread exits unexpectedly,
|
|
# but we'll try to join() them for a graceful shutdown.
|
|
background_thread = threading.Thread(target=run_background_tasks, name="HuntarrBackground", daemon=True)
|
|
background_thread.start()
|
|
|
|
# Start the web server in the main thread (blocking)
|
|
# This will run until the server is stopped (e.g., by Ctrl+C)
|
|
run_web_server()
|
|
|
|
except KeyboardInterrupt:
|
|
huntarr_logger.info("KeyboardInterrupt received in main thread. Shutting down...")
|
|
if not stop_event.is_set():
|
|
stop_event.set()
|
|
if not shutdown_requested.is_set():
|
|
shutdown_requested.set()
|
|
except Exception as e:
|
|
huntarr_logger.exception(f"An unexpected error occurred in the main execution block: {e}")
|
|
if not stop_event.is_set():
|
|
stop_event.set() # Ensure shutdown is triggered on unexpected errors
|
|
if not shutdown_requested.is_set():
|
|
shutdown_requested.set()
|
|
finally:
|
|
# --- Cleanup ---
|
|
huntarr_logger.info("Web server has stopped. Initiating final shutdown sequence...")
|
|
|
|
# Ensure the stop event is set (might already be set by signal handler or error)
|
|
if not stop_event.is_set():
|
|
huntarr_logger.warning("Stop event was not set before final cleanup. Setting now.")
|
|
stop_event.set()
|
|
|
|
# Wait for the background thread to finish cleanly
|
|
if background_thread and background_thread.is_alive():
|
|
huntarr_logger.info("Waiting for background tasks to complete...")
|
|
background_thread.join(timeout=5) # Reduced timeout for faster shutdown
|
|
|
|
if background_thread.is_alive():
|
|
huntarr_logger.warning("Background thread did not stop gracefully within the timeout.")
|
|
elif background_thread:
|
|
huntarr_logger.info("Background thread already stopped.")
|
|
else:
|
|
huntarr_logger.info("Background thread was not started.")
|
|
|
|
# Call the shutdown_threads function from primary.main (if it does more than just join)
|
|
# This might be redundant if start_huntarr handles its own cleanup via stop_event
|
|
# huntarr_logger.info("Calling shutdown_threads()...")
|
|
# shutdown_threads() # Uncomment if primary.main.shutdown_threads() does more cleanup
|
|
|
|
huntarr_logger.info("--- Huntarr Main Process Exiting ---")
|
|
|
|
# Return appropriate exit code based on shutdown reason
|
|
if shutdown_requested.is_set() or stop_event.is_set():
|
|
huntarr_logger.info("Clean shutdown completed - Exit code 0")
|
|
return 0 # Clean shutdown
|
|
else:
|
|
huntarr_logger.warning("Unexpected shutdown - Exit code 1")
|
|
return 1 # Unexpected shutdown
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Call the main function and exit with its return code
|
|
# This will use the return value from main() (0 for success) as the exit code
|
|
sys.exit(main()) |