mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Add application restart endpoint for post-update restarts
Adds POST /updater/restart endpoint that gracefully restarts the application using os.execv. Cleans up all decoder processes and global state before replacing the process with a fresh instance. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,14 +2,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
from flask import Blueprint, Response, jsonify, request
|
||||
|
||||
from utils.logging import get_logger
|
||||
from utils.updater import (
|
||||
check_for_updates,
|
||||
get_update_status,
|
||||
dismiss_update,
|
||||
get_update_status,
|
||||
perform_update,
|
||||
restart_application,
|
||||
)
|
||||
|
||||
logger = get_logger('intercept.routes.updater')
|
||||
@@ -137,3 +138,42 @@ def dismiss_notification() -> Response:
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@updater_bp.route('/restart', methods=['POST'])
|
||||
def restart_app() -> Response:
|
||||
"""
|
||||
Restart the application.
|
||||
|
||||
This endpoint triggers a graceful restart of the application:
|
||||
1. Stops all running decoder processes
|
||||
2. Cleans up global state
|
||||
3. Replaces the current process with a fresh instance
|
||||
|
||||
The response may not be received by the client since the process
|
||||
is replaced immediately. Clients should poll /health until the
|
||||
server responds again.
|
||||
|
||||
Returns:
|
||||
JSON with restart status (may not be delivered)
|
||||
"""
|
||||
import threading
|
||||
|
||||
logger.info("Restart requested via API")
|
||||
|
||||
# Send response before restarting
|
||||
# Use a short delay to allow the response to be sent
|
||||
def delayed_restart():
|
||||
import time
|
||||
time.sleep(0.5) # Allow response to be sent
|
||||
restart_application()
|
||||
|
||||
# Start restart in a background thread so we can return a response
|
||||
restart_thread = threading.Thread(target=delayed_restart, daemon=False)
|
||||
restart_thread.start()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Application is restarting. Please wait...',
|
||||
'action': 'restart'
|
||||
})
|
||||
|
||||
@@ -9,11 +9,12 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError, HTTPError
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
import config
|
||||
from utils.database import get_setting, set_setting
|
||||
@@ -509,6 +510,7 @@ def perform_update(stash_changes: bool = False) -> dict[str, Any]:
|
||||
'success': True,
|
||||
'updated': True,
|
||||
'message': 'Update successful! Please restart the application.',
|
||||
'restart_required': True,
|
||||
'requirements_changed': requirements_changed,
|
||||
'stashed': stashed,
|
||||
'stash_restored': stashed,
|
||||
@@ -527,3 +529,83 @@ def perform_update(stash_changes: bool = False) -> dict[str, Any]:
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def restart_application() -> dict[str, Any]:
|
||||
"""
|
||||
Restart the application using os.execv to replace the current process.
|
||||
|
||||
This function:
|
||||
1. Cleans up all running decoder processes
|
||||
2. Stops the cleanup manager
|
||||
3. Replaces the current process with a fresh Python interpreter
|
||||
|
||||
Returns:
|
||||
Dict with status (though this is typically not reached due to execv)
|
||||
"""
|
||||
import app as app_module
|
||||
from utils.cleanup import cleanup_manager
|
||||
from utils.process import cleanup_all_processes
|
||||
|
||||
logger.info("Application restart requested")
|
||||
|
||||
try:
|
||||
# Step 1: Kill all decoder processes
|
||||
logger.info("Stopping all decoder processes...")
|
||||
cleanup_all_processes()
|
||||
|
||||
# Step 2: Clear global process state
|
||||
with app_module.process_lock:
|
||||
app_module.current_process = None
|
||||
with app_module.sensor_lock:
|
||||
app_module.sensor_process = None
|
||||
with app_module.wifi_lock:
|
||||
app_module.wifi_process = None
|
||||
with app_module.adsb_lock:
|
||||
app_module.adsb_process = None
|
||||
with app_module.ais_lock:
|
||||
app_module.ais_process = None
|
||||
with app_module.acars_lock:
|
||||
app_module.acars_process = None
|
||||
with app_module.aprs_lock:
|
||||
app_module.aprs_process = None
|
||||
app_module.aprs_rtl_process = None
|
||||
with app_module.dsc_lock:
|
||||
app_module.dsc_process = None
|
||||
app_module.dsc_rtl_process = None
|
||||
|
||||
# Step 3: Clear SDR device registry
|
||||
with app_module.sdr_device_registry_lock:
|
||||
app_module.sdr_device_registry.clear()
|
||||
|
||||
# Step 4: Stop cleanup manager
|
||||
logger.info("Stopping cleanup manager...")
|
||||
cleanup_manager.stop()
|
||||
|
||||
# Step 5: Prepare for restart using os.execv
|
||||
# Get the Python executable and script path
|
||||
python_executable = sys.executable
|
||||
script_path = os.path.abspath(sys.argv[0])
|
||||
|
||||
# Build argument list (preserve original command-line args)
|
||||
args = [python_executable, script_path] + sys.argv[1:]
|
||||
|
||||
logger.info(f"Restarting with: {' '.join(args)}")
|
||||
|
||||
# Flush any pending log output
|
||||
logging.shutdown()
|
||||
|
||||
# Use os.execv to replace the current process
|
||||
# This will not return - the process is replaced entirely
|
||||
os.execv(python_executable, args)
|
||||
|
||||
# This code is never reached
|
||||
return {'success': True, 'message': 'Restarting...'}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Restart failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'message': 'Failed to restart application. Please restart manually.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user