#!/bin/bash # Kivy Signage Player Installation Script # Supports both online and offline installation set -e # Cleanup handler for Ctrl+C / unexpected exit cleanup() { echo "" echo "⚠️ Installation interrupted. Cleaning up..." # Nothing critical to clean, but inform the user echo "If the script was modifying system files, you may need to re-run." exit 1 } trap cleanup SIGINT SIGTERM SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$SCRIPT_DIR/repo" WHEELS_DIR="$REPO_DIR/python-wheels" SYSTEM_DIR="$REPO_DIR/system-packages" DEB_DIR="$SYSTEM_DIR/debs" # Helper to find the correct boot config file (Raspberry Pi) find_boot_config() { if [ -f /boot/firmware/config.txt ]; then echo "/boot/firmware/config.txt" elif [ -f /boot/config.txt ]; then echo "/boot/config.txt" else echo "" fi } # Function to detect Raspberry Pi detect_raspberry_pi() { BOOT_CONFIG=$(find_boot_config) if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null || [ -n "$BOOT_CONFIG" ]; then return 0 # Is Raspberry Pi else return 1 # Not Raspberry Pi fi } # Function to setup autostart workflow setup_autostart() { echo "" echo "==========================================" echo "Setting up Autostart Workflow" echo "==========================================" # Determine the actual user (if running with sudo) ACTUAL_USER="${SUDO_USER:-$(whoami)}" ACTUAL_HOME=$(eval echo ~"$ACTUAL_USER") AUTOSTART_DIR="$ACTUAL_HOME/.config/autostart" SYSTEMD_DIR="$ACTUAL_HOME/.config/systemd/user" LXDE_AUTOSTART="$ACTUAL_HOME/.config/lxsession/LXDE-pi/autostart" # Method 1: XDG Autostart (Primary - works with Wayland/GNOME/KDE) echo "Creating XDG autostart entry for Wayland..." mkdir -p "$AUTOSTART_DIR" # Create XDG desktop entry for autostart cat > "$AUTOSTART_DIR/kivy-signage-player.desktop" << 'EOF' [Desktop Entry] Type=Application Name=Kivy Signage Player Comment=Digital Signage Player Exec=/bin/bash -c "cd $SCRIPT_DIR && exec bash start.sh" Icon=media-video-display Categories=Utility; NoDisplay=false Terminal=false StartupNotify=false Hidden=false X-GNOME-Autostart-enabled=true X-GNOME-Autostart-delay=3 X-XFCE-Autostart-Override=true EOF # Replace $SCRIPT_DIR with actual path in the file sed -i "s|\$SCRIPT_DIR|$SCRIPT_DIR|g" "$AUTOSTART_DIR/kivy-signage-player.desktop" chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/kivy-signage-player.desktop" chmod 644 "$AUTOSTART_DIR/kivy-signage-player.desktop" echo "✓ XDG autostart entry created for Wayland session" # Also create Wayland session directory entry (for GNOME/Wayland) WAYLAND_AUTOSTART_DIR="$ACTUAL_HOME/.config/autostart.gnome" mkdir -p "$WAYLAND_AUTOSTART_DIR" cp "$AUTOSTART_DIR/kivy-signage-player.desktop" "$WAYLAND_AUTOSTART_DIR/kivy-signage-player.desktop" chown "$ACTUAL_USER:$ACTUAL_USER" "$WAYLAND_AUTOSTART_DIR/kivy-signage-player.desktop" echo "✓ XDG autostart also added to GNOME/Wayland session directory" # Method 2: LXDE Autostart (for Raspberry Pi OS with LXDE) if [ -f "$LXDE_AUTOSTART" ]; then echo "Configuring LXDE autostart..." if ! grep -q "kivy-signage-player" "$LXDE_AUTOSTART"; then echo "" >> "$LXDE_AUTOSTART" echo "# Kivy Signage Player autostart" >> "$LXDE_AUTOSTART" echo "@bash -c 'cd $SCRIPT_DIR && bash start.sh'" >> "$LXDE_AUTOSTART" chown "$ACTUAL_USER:$ACTUAL_USER" "$LXDE_AUTOSTART" echo "✓ Added to LXDE autostart" fi fi # Method 3: Disable systemd service (prefer Wayland session management) # XDG autostart above is sufficient for Wayland/GNOME sessions echo "Note: Using Wayland session autostart via XDG instead of systemd service" # If systemd service exists from previous installation, disable it if sudo test -f /etc/systemd/system/kiwy-player.service 2>/dev/null; then echo "Disabling old systemd service in favor of Wayland session..." sudo systemctl disable kiwy-player.service 2>/dev/null || true echo "✓ Old systemd service disabled" fi # Method 4: Cron job for fallback (starts at reboot) echo "Setting up cron fallback..." # Create a wrapper script that waits for desktop to be ready CRON_WRAPPER="$SCRIPT_DIR/.start-player-cron.sh" cat > "$CRON_WRAPPER" << 'EOF' #!/bin/bash # Wait for desktop environment to be ready sleep 15 # Start the player cd "$SCRIPT_DIR" && bash start.sh EOF sed -i "s|\$SCRIPT_DIR|$SCRIPT_DIR|g" "$CRON_WRAPPER" chmod +x "$CRON_WRAPPER" # Add to crontab for the actual user CRON_ENTRY="@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1" if ! su - "$ACTUAL_USER" -c "crontab -l 2>/dev/null" | grep -q "kivy-signage-player"; then su - "$ACTUAL_USER" -c "(crontab -l 2>/dev/null || true; echo '$CRON_ENTRY') | crontab -" 2>/dev/null || true echo "✓ Cron fallback configured" fi echo "" echo "Autostart configuration methods:" echo " 1. ✓ XDG Autostart Entry (~/.config/autostart/)" if [ -f "$LXDE_AUTOSTART" ]; then echo " 2. ✓ LXDE Autostart" fi echo " 3. ✓ Cron Fallback (@reboot)" echo "" echo "The player will start automatically when the desktop loads via XDG autostart." echo "To verify: ls -la ~/.config/autostart/kivy-signage-player.desktop" echo "" } # Function to disable power-saving mode on Raspberry Pi setup_raspberry_pi_power_management() { echo "" echo "==========================================" echo "Raspberry Pi Detected - Configuring Power Management" echo "==========================================" # Determine the actual user (if running with sudo) ACTUAL_USER="${SUDO_USER:-$(whoami)}" ACTUAL_HOME=$(eval echo ~"$ACTUAL_USER") # Disable HDMI power-saving echo "Disabling HDMI screen power-saving..." BOOT_CONFIG=$(find_boot_config) if [ -n "$BOOT_CONFIG" ]; then # Check if hdmi_blanking is already configured if grep -q "hdmi_blanking" "$BOOT_CONFIG"; then sudo sed -i 's/^hdmi_blanking=.*/hdmi_blanking=0/' "$BOOT_CONFIG" else echo "hdmi_blanking=0" | sudo tee -a "$BOOT_CONFIG" > /dev/null fi # Disable HDMI CEC (can interfere with power management) if grep -q "hdmi_ignore_cec_init" "$BOOT_CONFIG"; then sudo sed -i 's/^hdmi_ignore_cec_init=.*/hdmi_ignore_cec_init=1/' "$BOOT_CONFIG" else echo "hdmi_ignore_cec_init=1" | sudo tee -a "$BOOT_CONFIG" > /dev/null fi # Force HDMI mode to always be on if grep -q "hdmi_force_hotplug" "$BOOT_CONFIG"; then sudo sed -i 's/^hdmi_force_hotplug=.*/hdmi_force_hotplug=1/' "$BOOT_CONFIG" else echo "hdmi_force_hotplug=1" | sudo tee -a "$BOOT_CONFIG" > /dev/null fi # Disable HDMI sleep mode if grep -q "hdmi_ignore_edid" "$BOOT_CONFIG"; then sudo sed -i 's/^hdmi_ignore_edid=.*/hdmi_ignore_edid=0xa5000080/' "$BOOT_CONFIG" else echo "hdmi_ignore_edid=0xa5000080" | sudo tee -a "$BOOT_CONFIG" > /dev/null fi echo "✓ HDMI configuration locked in $BOOT_CONFIG" echo " - HDMI blanking disabled" echo " - HDMI CEC disabled" echo " - HDMI hotplug forced" echo " - HDMI sleep mode disabled" fi # Disable screensaver and screen blanking in X11 echo "Configuring display blanking settings..." # Create/update display configuration in home directory XORG_DIR="/etc/X11/xorg.conf.d" if [ -d "$XORG_DIR" ]; then # Create display power management configuration sudo tee "$XORG_DIR/99-no-blanking.conf" > /dev/null << 'EOF' Section "ServerFlags" Option "BlankTime" "0" Option "StandbyTime" "0" Option "SuspendTime" "0" Option "OffTime" "0" EndSection Section "InputClass" Identifier "Disable DPMS" MatchClass "DPMS" Option "DPMS" "false" EndSection EOF echo "✓ X11 display blanking disabled" fi # Disable CPU power scaling (keep at max performance) echo "Configuring CPU power management..." # Create systemd service to keep CPU at max frequency sudo tee /etc/systemd/system/disable-cpu-powersave.service > /dev/null << 'EOF' [Unit] Description=Disable CPU Power Saving for Signage Player After=network.target [Service] Type=oneshot ExecStart=/usr/bin/bash -c 'for cpu in /sys/devices/system/cpu/cpu[0-9]*; do echo performance > "$cpu/cpufreq/scaling_governor" 2>/dev/null; done' RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF # Enable and start the service sudo systemctl daemon-reload sudo systemctl enable disable-cpu-powersave.service sudo systemctl start disable-cpu-powersave.service echo "✓ CPU power saving disabled (performance mode enabled)" # Disable sleep/suspend echo "Disabling system sleep and suspend..." sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null || true echo "✓ System sleep/suspend disabled" # Disable screensaver via xset (if X11 is running) if command -v xset &> /dev/null; then # Create .xinitrc if it doesn't exist to disable screensaver if [ ! -f "$ACTUAL_HOME/.xinitrc" ]; then cat >> "$ACTUAL_HOME/.xinitrc" << 'EOF' # Disable screen blanking and screensaver xset s off xset -dpms xset s noblank EOF else # Update existing .xinitrc if needed if ! grep -q "xset s off" "$ACTUAL_HOME/.xinitrc"; then cat >> "$ACTUAL_HOME/.xinitrc" << 'EOF' # Disable screen blanking and screensaver xset s off xset -dpms xset s noblank EOF fi fi chown "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/.xinitrc" 2>/dev/null || true echo "✓ X11 screensaver disabled in $ACTUAL_HOME/.xinitrc" fi # Create screen keep-alive wrapper script echo "Creating screen keep-alive service..." KEEPALIVE_SCRIPT="$SCRIPT_DIR/.keep-screen-alive.sh" cat > "$KEEPALIVE_SCRIPT" << 'EOF' #!/bin/bash # Keep-screen-alive wrapper for player # Prevents screen from locking/turning off while player is running SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Function to keep screen awake keep_screen_awake() { while true; do # Move mouse slightly to prevent idle if command -v xdotool &> /dev/null; then xdotool mousemove_relative 1 1 xdotool mousemove_relative -1 -1 fi # Disable DPMS and screensaver periodically if command -v xset &> /dev/null; then xset s reset xset dpms force on fi sleep 30 done } # Function to inhibit systemd sleep (if available) inhibit_sleep() { if command -v systemd-inhibit &> /dev/null; then # Run player under systemd inhibit to prevent sleep systemd-inhibit --what=sleep --why="Signage player running" \ bash "$SCRIPT_DIR/start.sh" return $? fi return 1 } # Try systemd inhibit first (most reliable) if inhibit_sleep; then exit 0 fi # Fallback: Start keep-alive in background keep_screen_awake & KEEPALIVE_PID=$! # Start the player cd "$SCRIPT_DIR" bash start.sh PLAYER_EXIT=$? # Kill keep-alive when player exits kill $KEEPALIVE_PID 2>/dev/null || true exit $PLAYER_EXIT EOF chmod +x "$KEEPALIVE_SCRIPT" echo "✓ Screen keep-alive service created" # Create systemd service to keep HDMI powered on # Note: Using 5-minute interval to avoid flickering on some displays echo "Creating HDMI power control service..." sudo tee /etc/systemd/system/hdmi-poweron.service > /dev/null << 'EOF' [Unit] Description=Keep HDMI Display Powered On After=multi-user.target display-manager.service [Service] Type=simple ExecStart=/bin/bash -c 'while true; do /usr/bin/tvservice -p 2>/dev/null; sleep 300; done' Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable hdmi-poweron.service sudo systemctl start hdmi-poweron.service echo "✓ HDMI power control service enabled" # Create aggressive display keep-alive script echo "Creating aggressive display keep-alive script..." DISPLAY_KEEPALIVE="$SCRIPT_DIR/.display-keepalive.sh" cat > "$DISPLAY_KEEPALIVE" << 'EOF' #!/bin/bash # Aggressive display keep-alive for Raspberry Pi # Supports both X11 and Wayland environments DISPLAY_TIMEOUT=30 # Detect display server type detect_display_server() { if [ -n "$WAYLAND_DISPLAY" ]; then echo "wayland" elif [ -n "$DISPLAY" ]; then echo "x11" else echo "unknown" fi } DISPLAY_SERVER=$(detect_display_server) while true; do # Keep HDMI powered on (works for both X11 and Wayland) if command -v tvservice &> /dev/null; then /usr/bin/tvservice -p 2>/dev/null fi if [ "$DISPLAY_SERVER" = "wayland" ]; then # Wayland-specific power management # Method 1: Use wlr-randr for Wayland compositors (if available) if command -v wlr-randr &> /dev/null; then wlr-randr --output HDMI-A-1 --on 2>/dev/null || true fi # Method 2: Prevent idle using systemd-inhibit if command -v systemd-inhibit &> /dev/null; then # This is already running, but refresh the lock systemctl --user restart plasma-ksmserver.service 2>/dev/null || true fi # Method 3: Use wlopm (Wayland output power management) if command -v wlopm &> /dev/null; then wlopm --on \* 2>/dev/null || true fi # Method 4: Simulate activity via input (works on Wayland) if command -v ydotool &> /dev/null; then ydotool mousemove -x 1 -y 1 2>/dev/null || true ydotool mousemove -x -1 -y -1 2>/dev/null || true fi # Method 5: GNOME/KDE Wayland idle inhibit if command -v gnome-session-inhibit &> /dev/null; then # Already inhibited by running process true fi else # X11-specific power management (original code) if command -v xset &> /dev/null; then DISPLAY=:0 xset s off 2>/dev/null DISPLAY=:0 xset -dpms 2>/dev/null DISPLAY=:0 xset dpms force on 2>/dev/null DISPLAY=:0 xset s reset 2>/dev/null fi # Move mouse to trigger activity if command -v xdotool &> /dev/null; then DISPLAY=:0 xdotool mousemove_relative 1 1 2>/dev/null DISPLAY=:0 xdotool mousemove_relative -1 -1 2>/dev/null fi # Disable monitor power saving if command -v xrandr &> /dev/null; then DISPLAY=:0 xrandr --output HDMI-1 --power-profile performance 2>/dev/null || true fi fi sleep $DISPLAY_TIMEOUT done EOF chmod +x "$DISPLAY_KEEPALIVE" echo "✓ Display keep-alive script created" # Install Wayland tools if using Wayland echo "Checking for Wayland and installing necessary tools..." if [ -n "$WAYLAND_DISPLAY" ] || pgrep -x "wayfire\|sway\|weston\|mutter" > /dev/null 2>/dev/null; then echo "Wayland detected - installing Wayland power management tools..." # List of optional Wayland tools (best-effort install) WAYLAND_TOOLS="wlopm wlr-randr ydotool" for tool in $WAYLAND_TOOLS; do if ! command -v "$tool" &> /dev/null; then echo "Installing $tool (if available)..." sudo apt-get install -y "$tool" 2>/dev/null || echo "Note: $tool not available in package manager - skipping" fi done echo "✓ Wayland tools installation attempted" else echo "X11 detected - using xset/xdotool/xrandr tools" fi # Update systemd screen keep-alive service to use this script sudo tee /etc/systemd/system/screen-keepalive.service > /dev/null << EOF [Unit] Description=Keep Screen Awake During Signage Player After=display-manager.service PartOf=graphical-session.target [Service] Type=simple User=$ACTUAL_USER ExecStart=/bin/bash $DISPLAY_KEEPALIVE Restart=always RestartSec=5 Environment="DISPLAY=:0" Environment="XAUTHORITY=%h/.Xauthority" [Install] WantedBy=graphical-session.target EOF sudo systemctl daemon-reload sudo systemctl enable screen-keepalive.service echo "✓ Aggressive screen keep-alive service enabled" echo "" echo "⚠️ Note: Some changes require a system reboot to take effect." echo "Please rerun this script after rebooting if power management issues persist." echo "" } # ============================================================ # Config & Deployment Functions # ============================================================ # Function to write player configuration from CLI parameters write_player_config() { local config_dir="$SCRIPT_DIR/config" local config_file="$config_dir/app_config.json" echo "" echo "==========================================" echo "Configuring Player Settings" echo "==========================================" mkdir -p "$config_dir" # If --provision was given, fetch config from server if [ -n "$PROVISION_URL" ]; then echo "Fetching provisioning data from: $PROVISION_URL" local response response=$(curl -sSf "$PROVISION_URL" 2>&1 || true) if [ -z "$response" ]; then echo "⚠️ WARNING: Failed to fetch provisioning data from $PROVISION_URL" echo " The player will start with setup screen on first boot." echo " You can configure it manually via the touchscreen UI." return 0 fi # Write fetched config echo "$response" > "$config_file" echo "✓ Provisioning data saved to $config_file" # Extract key fields from response for display local fetched_name fetched_name=$(echo "$response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('screen_name',''))" 2>/dev/null || echo "unknown") echo " Provisioned screen: $fetched_name" return 0 fi # If --config was given, copy from file if [ -n "$CONFIG_FILE_PATH" ]; then if [ -f "$CONFIG_FILE_PATH" ]; then cp "$CONFIG_FILE_PATH" "$config_file" echo "✓ Configuration copied from $CONFIG_FILE_PATH" return 0 else echo "⚠️ WARNING: Config file not found: $CONFIG_FILE_PATH" fi fi # Build config from individual CLI params if any were provided if [ -n "$SERVER_IP" ] || [ -n "$SCREEN_NAME" ] || [ -n "$QUICKCONNECT_KEY" ]; then # Export vars so Python subprocess can read them safely export SERVER_IP SERVER_PORT SCREEN_NAME QUICKCONNECT_KEY export ORIENTATION TOUCH_ENABLED MAX_RESOLUTION export USE_HTTPS VERIFY_SSL EDIT_ENABLED # Use Python to merge existing config with CLI params (avoids bash quoting issues) python3 << PYEOF import json, os config_file = "$config_file" # Load existing config or start fresh if os.path.exists(config_file): with open(config_file) as f: config = json.load(f) else: config = {} # CLI parameter values (passed via environment for safety) cli_params = { 'server_ip': os.environ.get('SERVER_IP', ''), 'port': os.environ.get('SERVER_PORT', ''), 'screen_name': os.environ.get('SCREEN_NAME', ''), 'quickconnect_key': os.environ.get('QUICKCONNECT_KEY', ''), 'orientation': os.environ.get('ORIENTATION', ''), 'touch': os.environ.get('TOUCH_ENABLED', ''), 'max_resolution': os.environ.get('MAX_RESOLUTION', ''), } # Boolean flags need special handling https_val = os.environ.get('USE_HTTPS', '') verify_val = os.environ.get('VERIFY_SSL', '') edit_val = os.environ.get('EDIT_ENABLED', '') for key, val in cli_params.items(): if val: # Only override if a value was provided config[key] = val # Handle boolean fields if https_val: config['use_https'] = https_val.lower() == 'true' if verify_val: config['verify_ssl'] = verify_val.lower() == 'false' # --no-verify-ssl sets to "false" if edit_val: config['edit_feature_enabled'] = edit_val.lower() == 'true' # Ensure defaults for any missing fields config.setdefault('port', '8080') config.setdefault('orientation', 'Landscape') config.setdefault('touch', 'True') config.setdefault('max_resolution', '1920x1080') config.setdefault('edit_feature_enabled', False) config.setdefault('use_https', False) config.setdefault('verify_ssl', True) with open(config_file, 'w') as f: json.dump(config, f, indent=2) print("✓ Configuration written to " + config_file) PYEOF fi # If no config at all, create a minimal placeholder if [ ! -f "$config_file" ]; then cat > "$config_file" << 'EOF' { "server_ip": "", "port": "8080", "screen_name": "", "quickconnect_key": "", "orientation": "Landscape", "touch": "True", "max_resolution": "1920x1080", "edit_feature_enabled": false, "use_https": false, "verify_ssl": true } EOF echo "✓ Default config template created — please configure via touchscreen UI" fi } # Function to create required player directories setup_player_directories() { echo "" echo "==========================================" echo "Setting Up Player Directories" echo "==========================================" mkdir -p "$SCRIPT_DIR/media" mkdir -p "$SCRIPT_DIR/playlists" mkdir -p "$SCRIPT_DIR/config/resources" mkdir -p "$SCRIPT_DIR/logs" # Create a log rotation mechanism placeholder cat > "$SCRIPT_DIR/logs/.gitkeep" 2>/dev/null || true echo "✓ Player directories created:" echo " - media/ (downloaded media files)" echo " - playlists/ (synced playlists)" echo " - config/resources/ (resource files)" echo " - logs/ (player log output)" } # Function to initialize player authentication with Digiserver init_player_auth() { echo "" echo "==========================================" echo "Initializing Player Authentication" echo "==========================================" local config_file="$SCRIPT_DIR/config/app_config.json" if [ ! -f "$config_file" ]; then echo "⚠️ No config file found — skipping authentication" echo " Authentication will happen on first player run." return 0 fi # Check if we have enough config to authenticate local server_ip local screen_name local quickconnect_key server_ip=$(python3 -c "import json; print(json.load(open('$config_file')).get('server_ip',''))" 2>/dev/null || echo "") screen_name=$(python3 -c "import json; print(json.load(open('$config_file')).get('screen_name',''))" 2>/dev/null || echo "") quickconnect_key=$(python3 -c "import json; print(json.load(open('$config_file')).get('quickconnect_key',''))" 2>/dev/null || echo "") if [ -z "$server_ip" ] || [ -z "$screen_name" ] || [ -z "$quickconnect_key" ]; then echo "⏸️ Incomplete config (server, name, or key missing)" echo " Authentication will happen on first player run via the UI." return 0 fi echo "Attempting to register player with Digiserver..." echo " Server: $server_ip" echo " Screen: $screen_name" # Activate venv and run auth ( source "$SCRIPT_DIR/.venv/bin/activate" 2>/dev/null cd "$SCRIPT_DIR/src" python3 -c " import json, sys, asyncio sys.path.insert(0, '.') from get_playlists_v2 import ensure_authenticated config_file = '$config_file' with open(config_file) as f: config = json.load(f) auth = ensure_authenticated(config) if auth: print('✅ Player registered with server successfully') sys.exit(0) else: print('⚠️ Authentication pending — will retry on player startup') sys.exit(1) " 2>&1 || echo "⚠️ Auth initialization deferred to first player run" ) } # Function to optimize for background execution optimize_background_execution() { echo "" echo "==========================================" echo "Optimizing Background Execution" echo "==========================================" # Create a nohup wrapper that ensures the player runs in background # even if the terminal session ends NOHUP_WRAPPER="$SCRIPT_DIR/.run-background.sh" cat > "$NOHUP_WRAPPER" << 'EOF' #!/bin/bash # Background runner for Kiwy Signage Player # Ensures the player keeps running even after SSH logout SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$SCRIPT_DIR/logs/player-background.log" # Ensure log directory exists mkdir -p "$(dirname "$LOG_FILE")" # Log startup echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting player in background..." >> "$LOG_FILE" # Source the virtual environment cd "$SCRIPT_DIR" || exit 1 source .venv/bin/activate 2>/dev/null # Use nohup to ignore HUP signals nohup bash start.sh >> "$LOG_FILE" 2>&1 & PLAYER_PID=$! echo "[$(date '+%Y-%m-%d %H:%M:%S')] Player started with PID: $PLAYER_PID" >> "$LOG_FILE" echo "Player started in background (PID: $PLAYER_PID)" echo "Logs: $LOG_FILE" EOF chmod +x "$NOHUP_WRAPPER" echo "✓ Background runner created: .run-background.sh" echo " Usage: bash .run-background.sh" # Also ensure the autostart .desktop file uses the venv local autostart_file="$ACTUAL_HOME/.config/autostart/kivy-signage-player.desktop" if [ -f "$autostart_file" ]; then # The desktop entry already points to start.sh which handles venv echo "✓ Autostart entry already configured for background execution" fi # Create a status-check alias for convenience echo "" echo "Background management commands:" echo " bash .run-background.sh Start player in background" echo " bash check_player_status.sh Check if player is running" echo " bash stop_player.sh Stop the player" echo "" } # ============================================================ # CLI Argument Parsing # ============================================================ OFFLINE_MODE=false SERVER_IP="" SERVER_PORT="" SCREEN_NAME="" QUICKCONNECT_KEY="" USE_HTTPS="" VERIFY_SSL="" ORIENTATION="" TOUCH_ENABLED="" MAX_RESOLUTION="" EDIT_ENABLED="" PROVISION_URL="" CONFIG_FILE_PATH="" usage() { echo "Usage: $0 [OPTIONS]" echo "" echo "Installation modes (choose one):" echo " --provision URL One-time provisioning URL from Digiserver (fetches all config)" echo " --config FILE Path to pre-made JSON config file" echo " (individual params) Set each config value manually" echo "" echo "Configuration options:" echo " -s, --server IP Digiserver IP or hostname" echo " -p, --port PORT Server port (default: 8080)" echo " -n, --name NAME Screen/player name" echo " -k, --key KEY Quick-connect / auth key" echo " --https Enable HTTPS for server communication" echo " --no-verify-ssl Disable SSL certificate verification" echo " -o, --orientation Landscape or Portrait (default: Landscape)" echo " --touch Enable touch input" echo " -r, --resolution WxH Max resolution (default: 1920x1080)" echo " --edit Enable on-device edit feature" echo "" echo "Installation options:" echo " --offline Force offline installation from local packages" echo " -q, --quiet Silent mode (minimal output)" echo " -h, --help Show this help" exit 0 } while [ $# -gt 0 ]; do case "$1" in --provision) PROVISION_URL="$2" shift 2 ;; --config) CONFIG_FILE_PATH="$2" shift 2 ;; -s|--server) SERVER_IP="$2" shift 2 ;; -p|--port) SERVER_PORT="$2" shift 2 ;; -n|--name) SCREEN_NAME="$2" shift 2 ;; -k|--key) QUICKCONNECT_KEY="$2" shift 2 ;; --https) USE_HTTPS="true" shift ;; --no-verify-ssl) VERIFY_SSL="false" shift ;; -o|--orientation) ORIENTATION="$2" shift 2 ;; --touch) TOUCH_ENABLED="True" shift ;; -r|--resolution) MAX_RESOLUTION="$2" shift 2 ;; --edit) EDIT_ENABLED="true" shift ;; --offline) OFFLINE_MODE=true shift ;; -q|--quiet) # Silent mode - redirect stdout to log file exec > >(tee -a /tmp/kivy-install.log) 2>&1 shift ;; -h|--help) usage ;; *) echo "Unknown option: $1" usage ;; esac done # Auto-detect offline mode if wheels exist if [ "$OFFLINE_MODE" = false ] && [ -d "$WHEELS_DIR" ] && [ "$(ls -A "$WHEELS_DIR"/*.whl 2>/dev/null)" ]; then OFFLINE_MODE=true fi # ============================================================ # Mode Banner # ============================================================ if [ "$OFFLINE_MODE" = true ]; then echo "==========================================" echo "📦 Offline Installation Mode" echo "==========================================" else echo "==========================================" echo "🌐 Online Installation Mode" echo "==========================================" fi echo "" echo "Installing Kivy Signage Player dependencies..." echo "" # Install system dependencies echo "Step 1: Installing system dependencies..." echo "--------------------" if [ "$OFFLINE_MODE" = true ] && [ -d "$DEB_DIR" ] && [ "$(ls -A $DEB_DIR/*.deb 2>/dev/null)" ]; then # Offline: Install from local .deb files echo "Installing from offline .deb packages..." cd "$DEB_DIR" # Install all .deb files with dependencies sudo dpkg -i *.deb 2>/dev/null || true # Fix any broken dependencies sudo apt-get install -f -y || true echo "System packages installed from offline repository" else # Online: Use apt-get echo "Updating package lists..." sudo apt update echo "Installing system packages..." # Read packages from file if available, otherwise use default list if [ -f "$SYSTEM_DIR/apt-packages.txt" ]; then PACKAGES=$(grep -v '^#' "$SYSTEM_DIR/apt-packages.txt" | grep -v '^$' | tr '\n' ' ') sudo apt install -y $PACKAGES else # Default package list sudo apt install -y python3-pip python3-setuptools python3-dev sudo apt install -y libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev sudo apt install -y libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev sudo apt install -y zlib1g-dev ffmpeg libavcodec-extra sudo apt install -y gstreamer1.0-plugins-base gstreamer1.0-plugins-good fi echo "System packages installed successfully" fi echo "" # Install Python dependencies into virtual environment echo "Step 2: Creating virtual environment and installing Python dependencies..." echo "--------------------" # Create virtual environment if it doesn't exist if [ ! -d "$SCRIPT_DIR/.venv" ]; then echo "Creating Python virtual environment..." python3 -m venv "$SCRIPT_DIR/.venv" echo "✓ Virtual environment created at .venv" else echo "✓ Virtual environment already exists" fi # Activate the virtual environment source "$SCRIPT_DIR/.venv/bin/activate" # Upgrade pip inside the virtual environment echo "Upgrading pip..." pip install --upgrade pip 2>/dev/null || true if [ "$OFFLINE_MODE" = true ] && [ -d "$WHEELS_DIR" ] && [ "$(ls -A "$WHEELS_DIR"/*.whl 2>/dev/null)" ]; then # Offline: Install from local wheels echo "Installing from offline Python wheels..." echo "Wheel files found: $(ls -1 "$WHEELS_DIR"/*.whl 2>/dev/null | wc -l)" if pip install --no-index --find-links="$WHEELS_DIR" -r requirements.txt 2>&1 | tee /tmp/pip_install.log; then echo "✓ Python packages installed from offline repository" else echo "⚠️ Warning: Offline installation failed (possibly due to Python version mismatch)" echo "Falling back to online installation..." pip install -r requirements.txt echo "✓ Python packages installed from PyPI" fi else # Online: Use pip from PyPI echo "Installing from PyPI into virtual environment..." pip install -r requirements.txt echo "✓ Python packages installed successfully into .venv" fi # Deactivate the virtual environment (re-activate at runtime) deactivate # ============================================================ # Post-Installation Setup # ============================================================ # Setup autostart workflow setup_autostart # Check if running on Raspberry Pi and setup power management if detect_raspberry_pi; then setup_raspberry_pi_power_management fi # Optimize video playback for Raspberry Pi if detect_raspberry_pi; then echo "" echo "==========================================" echo "Optimizing for Video Playback" echo "==========================================" # Run video optimization script if [ -f "$SCRIPT_DIR/.video-optimization.sh" ]; then bash "$SCRIPT_DIR/.video-optimization.sh" fi fi # Create required player directories setup_player_directories # Write player configuration write_player_config # Initialize authentication with Digiserver init_player_auth # Optimize for background execution optimize_background_execution # ============================================================ # Final Summary # ============================================================ echo "" echo "==========================================" echo "✅ Kivy Signage Player — Installation Complete!" echo "==========================================" echo "" echo "📁 Installation directory: $SCRIPT_DIR" echo "🐍 Virtual environment: .venv/" echo "" # Read back the config to show applied settings if [ -f "$SCRIPT_DIR/config/app_config.json" ]; then echo "📋 Player Configuration:" echo "------------------------" python3 -c " import json with open('$SCRIPT_DIR/config/app_config.json') as f: c = json.load(f) print(f' Server: {c.get(\"server_ip\",\"not set\")}') print(f' Port: {c.get(\"port\",\"8080\")}') print(f' Screen: {c.get(\"screen_name\",\"not set\")}') print(f' HTTPS: {c.get(\"use_https\",False)}') print(f' Resolution: {c.get(\"max_resolution\",\"auto\")}') " 2>/dev/null || echo " (config present)" echo "" fi echo "🚀 Quick Start Commands:" echo "------------------------" echo " bash start.sh Start with watchdog (auto-restart)" echo " bash run_player.sh Run once (no auto-restart)" echo " bash .run-background.sh Run in background (survives SSH logout)" echo " bash check_player_status.sh Check if player is running" echo " bash stop_player.sh Stop the player" echo "" echo "🔄 Autostart: The player will launch automatically on next desktop login." echo "" echo "📝 Logs: $SCRIPT_DIR/logs/" echo "" # ============================================================ # Reboot Prompt # ============================================================ echo "" echo "==========================================" echo "🔄 System Reboot Required" echo "==========================================" echo "" echo "Autostart and power-saving settings are configured." echo "A reboot is required to apply all changes." echo "" # Auto-reboot with 5-second window to cancel echo "" echo "🔄 Rebooting in 5 seconds... Press Ctrl+C to cancel." sleep 5 echo "Rebooting now..." sudo reboot