#!/bin/bash # Kivy Signage Player Startup Script with Watchdog # This script monitors and auto-restarts the player if it crashes # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Configuration MAX_RETRIES=999999 # Effectively unlimited retries RESTART_DELAY=5 # Seconds to wait before restart HEALTH_CHECK_INTERVAL=30 # Seconds between health checks HEARTBEAT_FILE="$SCRIPT_DIR/.player_heartbeat" STOP_FLAG_FILE="$SCRIPT_DIR/.player_stop_requested" LOG_FILE="$SCRIPT_DIR/player_watchdog.log" # Ensure log file is writable if [ ! -w "$(dirname "$LOG_FILE")" ]; then LOG_FILE="/tmp/kivy-player-watchdog.log" fi # Function to log messages (MUST be defined before use) log_message() { local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" echo "$msg" # Try to write to log file, ignore errors if permission denied echo "$msg" >> "$LOG_FILE" 2>/dev/null || true } # Load user's environment from systemd user session (most reliable method) # This ensures we get the proper DISPLAY/WAYLAND_DISPLAY and session variables load_user_environment() { # Try to get environment from active user session via systemctl local user_env if command -v systemctl &>/dev/null; then user_env=$(systemctl --user show-environment 2>/dev/null) if [ -n "$user_env" ]; then # Extract display-related variables without subshell to preserve exports while IFS='=' read -r key value; do case "$key" in DISPLAY|WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION_BUS_ADDRESS) export "$key=$value" ;; esac done <<< "$user_env" log_message "Loaded user environment from systemctl --user" # Verify we got valid display if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; then log_message "Display environment ready: DISPLAY='$DISPLAY' WAYLAND_DISPLAY='$WAYLAND_DISPLAY'" return 0 else log_message "Systemctl didn't provide display variables, trying fallback..." fi fi fi # Fallback: detect display manually log_message "Falling back to manual display detection..." if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then # Try to detect Wayland display if [ -S "/run/user/$(id -u)/wayland-0" ]; then export WAYLAND_DISPLAY=wayland-0 log_message "Detected Wayland display: $WAYLAND_DISPLAY" # Try to detect X11 display elif [ -S "/tmp/.X11-unix/X0" ]; then export DISPLAY=:0 log_message "Detected X11 display: $DISPLAY" else # Wait for display to come up (useful for systemd or delayed starts) log_message "Waiting for display server to be ready (up to 30 seconds)..." for i in {1..30}; do sleep 1 if [ -S "/run/user/$(id -u)/wayland-0" ] 2>/dev/null; then export WAYLAND_DISPLAY=wayland-0 log_message "Display server detected on attempt $i: Wayland" return 0 elif [ -S "/tmp/.X11-unix/X0" ] 2>/dev/null; then export DISPLAY=:0 log_message "Display server detected on attempt $i: X11" return 0 fi done fi fi # Verify we have a display now if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then log_message "WARNING: No display server detected. This may cause graphics issues." return 1 fi log_message "Display environment ready: DISPLAY=$DISPLAY WAYLAND_DISPLAY=$WAYLAND_DISPLAY" return 0 } # Load the user environment early load_user_environment # Function to check if player is healthy check_health() { # Check if heartbeat file exists and is recent (within last 60 seconds) if [ -f "$HEARTBEAT_FILE" ]; then local last_update=$(stat -c %Y "$HEARTBEAT_FILE" 2>/dev/null || echo 0) local current_time=$(date +%s) local diff=$((current_time - last_update)) if [ $diff -lt 60 ]; then return 0 # Healthy else log_message "⚠️ Player heartbeat stale (${diff}s old)" return 1 # Unhealthy fi else # If heartbeat file doesn't exist yet, assume player is starting return 0 fi } # Cleanup function cleanup() { log_message "🛑 Watchdog received stop signal" rm -f "$HEARTBEAT_FILE" rm -f "$STOP_FLAG_FILE" exit 0 } # Trap signals for graceful shutdown trap cleanup SIGINT SIGTERM log_message "==========================================" log_message "🚀 Kivy Signage Player Watchdog Started" log_message "==========================================" log_message "Project directory: $SCRIPT_DIR" log_message "Max retries: Unlimited" log_message "Restart delay: ${RESTART_DELAY}s" log_message "" # Remove old stop flag if exists (fresh start) rm -f "$STOP_FLAG_FILE" # Change to the project directory cd "$SCRIPT_DIR" # Check if virtual environment exists if [ -d ".venv" ]; then log_message "✓ Virtual environment found" source .venv/bin/activate else log_message "⚠️ Creating virtual environment..." python3 -m venv .venv source .venv/bin/activate log_message "📦 Installing dependencies..." pip3 install -r requirements.txt log_message "✓ Virtual environment ready" fi # Check if configuration exists if [ ! -f "config/app_config.json" ]; then log_message "⚠️ WARNING: Configuration file not found!" log_message "Player may not function correctly without configuration" fi # Main watchdog loop retry_count=0 while true; do retry_count=$((retry_count + 1)) log_message "" log_message "==========================================" log_message "▶️ Starting player (attempt #${retry_count})" log_message "==========================================" # Clean old heartbeat rm -f "$HEARTBEAT_FILE" # Start the player cd "$SCRIPT_DIR/src" python3 main.py & PLAYER_PID=$! log_message "Player PID: $PLAYER_PID" # Monitor the player while true; do sleep $HEALTH_CHECK_INTERVAL # Check if process is still running if ! kill -0 $PLAYER_PID 2>/dev/null; then log_message "❌ Player process crashed or stopped (PID: $PLAYER_PID)" break fi # Check health via heartbeat if ! check_health; then log_message "❌ Player health check failed - may be frozen" kill $PLAYER_PID 2>/dev/null sleep 2 kill -9 $PLAYER_PID 2>/dev/null break fi # Player is healthy, continue monitoring done # Player stopped or crashed # Check if user requested intentional exit if [ -f "$STOP_FLAG_FILE" ]; then log_message "✋ Stop flag detected - user requested exit via password" log_message "Watchdog will NOT restart the player" log_message "To restart, run ./start.sh again" rm -f "$HEARTBEAT_FILE" break fi log_message "⏳ Waiting ${RESTART_DELAY}s before restart..." sleep $RESTART_DELAY # Cleanup any zombie processes pkill -9 -f "python3 main.py" 2>/dev/null done log_message "" log_message "==========================================" log_message "Watchdog stopped" log_message "==========================================" # Deactivate virtual environment (this line is never reached in watchdog mode) deactivate