c221f76a71
- Defaults now match run_player-afterclone.sh and the deployed device: edit_feature_enabled=true, verify_ssl=false (use_https=false unchanged) - Fix inverted logic: --no-verify-ssl previously set verify_ssl=True - Add --verify-ssl flag for parity with run_player-afterclone.sh
773 lines
25 KiB
Bash
Executable File
773 lines
25 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Kivy Signage Player Installation Script
|
|
# Supports both online and offline installation using a Python virtual environment
|
|
#
|
|
# Usage:
|
|
# bash install.sh [OPTIONS]
|
|
#
|
|
# Options:
|
|
# -U, --sudo-user USER Username for sudo (avoids password prompts)
|
|
# -W, --sudo-password PASS Password for sudo (use with caution)
|
|
# --offline Force offline installation from local packages
|
|
# -q, --quiet Silent mode
|
|
# -h, --help Show help
|
|
#
|
|
# Configuration options (for Digiserver):
|
|
# -s, --server IP Server IP/hostname
|
|
# -p, --port PORT Server port (default: 8080)
|
|
# -n, --name NAME Screen/player name
|
|
# -k, --key KEY Quick-connect / auth key
|
|
# --https Enable HTTPS
|
|
# --verify-ssl Enable SSL certificate verification
|
|
# --no-verify-ssl Disable SSL verification (default)
|
|
# -o, --orientation Landscape or Portrait
|
|
# --touch Enable touch input
|
|
# -r, --resolution WxH Max resolution
|
|
# --edit Enable on-device edit feature
|
|
# --provision URL One-time provisioning URL
|
|
# --config FILE Path to pre-made JSON config
|
|
|
|
set -e
|
|
|
|
# ============================================================
|
|
# Configuration
|
|
# ============================================================
|
|
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"
|
|
VENV_DIR="$SCRIPT_DIR/.venv"
|
|
REQUIREMENTS_FILE="$SCRIPT_DIR/requirements.txt"
|
|
SUDO_USERNAME=""
|
|
SUDO_PASSWORD=""
|
|
OFFLINE_MODE=false
|
|
PI_BOOT_PATCH_SCRIPT="$SCRIPT_DIR/scripts/pi_boot_config_patch_once.sh"
|
|
|
|
# ============================================================
|
|
# Helper: Run sudo with optional password
|
|
# ============================================================
|
|
run_sudo() {
|
|
if [ -n "$SUDO_PASSWORD" ]; then
|
|
echo "$SUDO_PASSWORD" | sudo -S "$@" 2>/dev/null
|
|
else
|
|
sudo "$@"
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# Helpers
|
|
# ============================================================
|
|
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
|
|
}
|
|
|
|
detect_raspberry_pi() {
|
|
BOOT_CONFIG=$(find_boot_config)
|
|
if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null || [ -n "$BOOT_CONFIG" ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# Helper: Internet reachability probe (dependency-free)
|
|
# ============================================================
|
|
# Returns 0 if the public internet (PyPI) is reachable, 1 otherwise.
|
|
# Uses only the Python stdlib so it works on a bare, offline host.
|
|
has_internet() {
|
|
python3 - << 'PYEOF' >/dev/null 2>&1
|
|
import socket
|
|
try:
|
|
socket.setdefaulttimeout(3)
|
|
socket.create_connection(("pypi.org", 443), timeout=3).close()
|
|
raise SystemExit(0)
|
|
except Exception:
|
|
raise SystemExit(1)
|
|
PYEOF
|
|
return $?
|
|
}
|
|
|
|
# ============================================================
|
|
# Helper: Fetch a URL using curl, wget, or the Python stdlib
|
|
# ============================================================
|
|
# Works on minimal/offline images where curl/wget may be absent.
|
|
fetch_url() {
|
|
local url="$1"
|
|
if command -v curl &>/dev/null; then
|
|
curl -sSf --max-time 20 "$url"
|
|
elif command -v wget &>/dev/null; then
|
|
wget -qO- --timeout=20 "$url"
|
|
else
|
|
python3 - "$url" << 'PYEOF'
|
|
import sys, urllib.request
|
|
url = sys.argv[1]
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=20) as r:
|
|
sys.stdout.write(r.read().decode("utf-8", "replace"))
|
|
except Exception as e:
|
|
sys.stderr.write("fetch failed: %s\n" % e)
|
|
raise SystemExit(1)
|
|
PYEOF
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# CLI Argument Parsing
|
|
# ============================================================
|
|
SERVER_IP=""
|
|
SERVER_PORT=""
|
|
SCREEN_NAME=""
|
|
QUICKCONNECT_KEY=""
|
|
USE_HTTPS=""
|
|
VERIFY_SSL=""
|
|
ORIENTATION=""
|
|
TOUCH_ENABLED=""
|
|
MAX_RESOLUTION=""
|
|
EDIT_ENABLED=""
|
|
PROVISION_URL=""
|
|
CONFIG_FILE_PATH=""
|
|
NO_REBOOT=false
|
|
|
|
usage() {
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Authentication options (avoids sudo password prompts):"
|
|
echo " -U, --sudo-user USER Username for sudo commands"
|
|
echo " -W, --sudo-password PASS Password for sudo commands"
|
|
echo ""
|
|
echo "Installation modes (choose one):"
|
|
echo " --provision URL One-time provisioning URL from Digiserver"
|
|
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 " --verify-ssl Enable SSL certificate verification"
|
|
echo " --no-verify-ssl Disable SSL certificate verification (default)"
|
|
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 " --no-reboot Do not reboot automatically at the end"
|
|
echo " -q, --quiet Silent mode (minimal output)"
|
|
echo " -h, --help Show this help"
|
|
exit 0
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-U|--sudo-user)
|
|
SUDO_USERNAME="$2"
|
|
shift 2
|
|
;;
|
|
-W|--sudo-password)
|
|
SUDO_PASSWORD="$2"
|
|
shift 2
|
|
;;
|
|
--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
|
|
;;
|
|
--verify-ssl)
|
|
VERIFY_SSL="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
|
|
;;
|
|
--no-reboot)
|
|
NO_REBOOT=true
|
|
shift
|
|
;;
|
|
-q|--quiet)
|
|
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
|
|
|
|
# Probe internet reachability once (guards every network step below)
|
|
HAS_INTERNET=false
|
|
if has_internet; then
|
|
HAS_INTERNET=true
|
|
fi
|
|
|
|
# ============================================================
|
|
# Mode Banner
|
|
# ============================================================
|
|
echo "=========================================="
|
|
echo "Kivy Signage Player v1.2.0 - Installer"
|
|
echo "=========================================="
|
|
if [ "$OFFLINE_MODE" = true ]; then
|
|
echo "Mode: Offline Installation"
|
|
else
|
|
echo "Mode: Online Installation"
|
|
fi
|
|
if [ "$HAS_INTERNET" = true ]; then
|
|
echo "Internet: reachable"
|
|
else
|
|
echo "Internet: NOT available (local-network / isolated install)"
|
|
fi
|
|
if [ -n "$SUDO_USERNAME" ]; then
|
|
echo "Sudo user: $SUDO_USERNAME"
|
|
fi
|
|
echo ""
|
|
|
|
# ============================================================
|
|
# Step 1: System Dependencies
|
|
# ============================================================
|
|
echo "Step 1/6: Installing system dependencies..."
|
|
echo "--------------------"
|
|
|
|
echo "Updating package lists..."
|
|
if [ "$HAS_INTERNET" = true ]; then
|
|
run_sudo apt update 2>/dev/null || echo " WARNING: 'apt update' failed; continuing with existing lists"
|
|
else
|
|
echo " Offline / no internet — skipping 'apt update'"
|
|
run_sudo apt update 2>/dev/null || true
|
|
fi
|
|
|
|
echo "Installing system packages..."
|
|
|
|
# System dependencies required for Kivy + video playback
|
|
APT_PACKAGES="python3-pip python3-setuptools python3-dev python3-venv \
|
|
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
|
|
libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev \
|
|
zlib1g-dev ffmpeg libavcodec-extra \
|
|
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
|
curl wget"
|
|
|
|
# Prefer the offline .deb bundle when present
|
|
USED_DEB_BUNDLE=false
|
|
if [ -d "$DEB_DIR" ] && ls "$DEB_DIR"/*.deb >/dev/null 2>&1; then
|
|
echo "Installing from offline .deb packages in $DEB_DIR..."
|
|
cd "$DEB_DIR"
|
|
run_sudo dpkg -i *.deb 2>/dev/null || true
|
|
# Resolve dependencies from the local bundle only (no-op if already satisfied)
|
|
run_sudo apt-get install -f -y 2>/dev/null || true
|
|
cd "$SCRIPT_DIR"
|
|
USED_DEB_BUNDLE=true
|
|
elif [ "$OFFLINE_MODE" = true ]; then
|
|
echo "WARNING: Offline mode but no .deb bundle found in $DEB_DIR"
|
|
echo " System packages will still be attempted via apt (may fail without internet)"
|
|
fi
|
|
|
|
# Install/complete system packages. Never abort the installer:
|
|
# offline, some packages may already be present (fine) or simply unavailable.
|
|
if run_sudo apt install -y $APT_PACKAGES 2>/dev/null; then
|
|
echo "System dependencies installed"
|
|
else
|
|
echo "WARNING: bulk system package install did not fully succeed."
|
|
echo " Installing available packages individually..."
|
|
for pkg in $APT_PACKAGES; do
|
|
run_sudo apt install -y "$pkg" 2>/dev/null || echo " - unavailable (skipped): $pkg"
|
|
done
|
|
if [ "$HAS_INTERNET" != true ] && [ "$USED_DEB_BUNDLE" != true ]; then
|
|
echo " NOTE: Host is offline and no .deb bundle was found."
|
|
echo " Add .deb files to $DEB_DIR to satisfy missing system packages."
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ============================================================
|
|
# Step 2: Python Virtual Environment
|
|
# ============================================================
|
|
echo "Step 2/6: Setting up Python virtual environment..."
|
|
echo "--------------------"
|
|
|
|
if [ -d "$VENV_DIR" ]; then
|
|
echo "Removing old virtual environment..."
|
|
rm -rf "$VENV_DIR"
|
|
fi
|
|
|
|
echo "Creating virtual environment (with system site-packages)..."
|
|
python3 -m venv --system-site-packages "$VENV_DIR"
|
|
echo "Virtual environment created at $VENV_DIR"
|
|
|
|
source "$VENV_DIR/bin/activate"
|
|
|
|
if [ "$OFFLINE_MODE" = true ] && [ "$HAS_INTERNET" != true ]; then
|
|
echo "Skipping pip self-upgrade (offline, no internet)"
|
|
else
|
|
echo "Upgrading pip..."
|
|
pip install --upgrade pip --quiet 2>/dev/null || echo " WARNING: pip upgrade failed; continuing"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ============================================================
|
|
# Step 3: Install Python Packages
|
|
# ============================================================
|
|
echo "Step 3/6: Installing Python packages..."
|
|
echo "--------------------"
|
|
|
|
PIP_EXTRA_ARGS=""
|
|
if [ "$OFFLINE_MODE" = true ]; then
|
|
echo "Using offline wheel repository: $WHEELS_DIR"
|
|
PIP_EXTRA_ARGS="--no-index --find-links=$WHEELS_DIR"
|
|
fi
|
|
|
|
if [ -f "$REQUIREMENTS_FILE" ]; then
|
|
echo "Installing packages from requirements.txt..."
|
|
if [ "$OFFLINE_MODE" = true ]; then
|
|
if pip install $PIP_EXTRA_ARGS -r "$REQUIREMENTS_FILE"; then
|
|
echo "All packages installed from offline repository"
|
|
else
|
|
echo ""
|
|
echo "Offline installation failed (some wheels may be missing or incompatible)"
|
|
if [ "$HAS_INTERNET" = true ]; then
|
|
echo " Falling back to online installation (requires internet)..."
|
|
pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true
|
|
else
|
|
echo " No internet available — cannot fall back to online installation."
|
|
echo " Add the missing/compatible wheels to: $WHEELS_DIR"
|
|
fi
|
|
fi
|
|
else
|
|
pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true
|
|
fi
|
|
|
|
# Verify critical packages are importable
|
|
echo ""
|
|
echo "Verifying installed packages..."
|
|
pip list --format=columns 2>/dev/null | grep -iE "kivy|ffpyplayer" || true
|
|
|
|
# If a critical package is missing, fall back to online install only when reachable
|
|
for pkg in kivy ffpyplayer; do
|
|
if python3 -c "import $pkg" 2>/dev/null; then
|
|
echo " $pkg OK"
|
|
else
|
|
echo " WARNING: $pkg could not be imported in the virtual environment"
|
|
if [ "$HAS_INTERNET" = true ]; then
|
|
echo " Attempting online fallback install for $pkg..."
|
|
pip install "$pkg" 2>/dev/null || echo " - $pkg online install failed (continuing)"
|
|
else
|
|
echo " Offline: ensure a compatible '$pkg' wheel exists in $WHEELS_DIR"
|
|
fi
|
|
fi
|
|
done
|
|
else
|
|
echo "requirements.txt not found - skipping pip install"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
deactivate
|
|
|
|
# ============================================================
|
|
# Step 4: Autostart Workflow
|
|
# ============================================================
|
|
echo "Step 4/6: Setting up autostart..."
|
|
echo "--------------------"
|
|
|
|
ACTUAL_USER="${SUDO_USERNAME:-${SUDO_USER:-$(whoami)}}"
|
|
ACTUAL_HOME=$(eval echo ~"$ACTUAL_USER")
|
|
AUTOSTART_DIR="$ACTUAL_HOME/.config/autostart"
|
|
|
|
echo "Configuring autostart for user: $ACTUAL_USER"
|
|
|
|
mkdir -p "$AUTOSTART_DIR"
|
|
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
|
|
|
|
run_sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/kivy-signage-player.desktop" 2>/dev/null || true
|
|
chmod 644 "$AUTOSTART_DIR/kivy-signage-player.desktop" 2>/dev/null || true
|
|
echo "XDG autostart entry created"
|
|
|
|
# Cron fallback (secondary to XDG autostart; start.sh enforces single-instance)
|
|
if command -v crontab &>/dev/null; then
|
|
CRON_WRAPPER="$SCRIPT_DIR/.start-player-cron.sh"
|
|
CRON_MARKER_TAG=".start-player-cron.sh"
|
|
cat > "$CRON_WRAPPER" << EOF
|
|
#!/bin/bash
|
|
# Kiwy signage player cron fallback (managed by install.sh)
|
|
sleep 15
|
|
cd $SCRIPT_DIR && bash start.sh
|
|
EOF
|
|
chmod +x "$CRON_WRAPPER"
|
|
|
|
# Idempotent: strip ALL previous Kiwy cron lines, then add exactly one.
|
|
# (The old guard grepped for a string that never appeared in the entry,
|
|
# so every re-install appended another @reboot line.)
|
|
CURRENT_CRON="$(sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null || true)"
|
|
CLEANED_CRON="$(printf '%s\n' "$CURRENT_CRON" \
|
|
| grep -v -F "$CRON_MARKER_TAG" \
|
|
| grep -v -F "$CRON_WRAPPER" \
|
|
| grep -v -F "# Kiwy signage player" || true)"
|
|
{
|
|
printf '%s\n' "$CLEANED_CRON" | grep -v '^$'
|
|
echo "# Kiwy signage player autostart fallback (managed by install.sh)"
|
|
echo "@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1"
|
|
} | sudo -u "$ACTUAL_USER" crontab - 2>/dev/null || true
|
|
echo "Cron fallback configured (single entry, deduplicated)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ============================================================
|
|
# Step 5: Raspberry Pi Optimization
|
|
# ============================================================
|
|
if detect_raspberry_pi; then
|
|
echo "Step 5/6: Optimizing for Raspberry Pi..."
|
|
echo "--------------------"
|
|
|
|
BOOT_CONFIG=$(find_boot_config)
|
|
if [ -n "$BOOT_CONFIG" ]; then
|
|
echo "Configuring HDMI settings..."
|
|
for setting in "hdmi_blanking=0" "hdmi_ignore_cec_init=1" "hdmi_force_hotplug=1"; do
|
|
key="${setting%%=*}"
|
|
if grep -q "^$key" "$BOOT_CONFIG" 2>/dev/null; then
|
|
run_sudo sed -i "s/^$key=.*/$setting/" "$BOOT_CONFIG"
|
|
else
|
|
echo "$setting" | run_sudo tee -a "$BOOT_CONFIG" > /dev/null
|
|
fi
|
|
done
|
|
echo "HDMI settings configured"
|
|
fi
|
|
|
|
echo "Setting CPU to performance mode..."
|
|
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
|
|
if [ -f "$cpu/cpufreq/scaling_governor" ]; then
|
|
echo performance | run_sudo tee "$cpu/cpufreq/scaling_governor" > /dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
|
|
run_sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null || true
|
|
echo "System sleep disabled"
|
|
|
|
if [ -f "$SCRIPT_DIR/.video-optimization.sh" ]; then
|
|
bash "$SCRIPT_DIR/.video-optimization.sh"
|
|
fi
|
|
|
|
echo ""
|
|
fi
|
|
|
|
# ============================================================
|
|
# Step 6: Finalize Player Setup
|
|
# ============================================================
|
|
echo "Step 6/6: Finalizing player setup..."
|
|
echo "--------------------"
|
|
|
|
# Create required directories
|
|
echo "Creating player directories..."
|
|
mkdir -p "$SCRIPT_DIR/media"
|
|
mkdir -p "$SCRIPT_DIR/playlists"
|
|
mkdir -p "$SCRIPT_DIR/config/resources"
|
|
mkdir -p "$SCRIPT_DIR/logs"
|
|
touch "$SCRIPT_DIR/logs/.gitkeep" 2>/dev/null || true
|
|
echo "Player directories created"
|
|
|
|
# Write player configuration
|
|
write_player_config() {
|
|
local config_dir="$SCRIPT_DIR/config"
|
|
local config_file="$config_dir/app_config.json"
|
|
mkdir -p "$config_dir"
|
|
|
|
if [ -n "$PROVISION_URL" ]; then
|
|
echo "Fetching provisioning data from: $PROVISION_URL"
|
|
local response
|
|
response=$(fetch_url "$PROVISION_URL" 2>/dev/null || true)
|
|
if [ -n "$response" ]; then
|
|
echo "$response" > "$config_file"
|
|
echo "Provisioning data saved"
|
|
return 0
|
|
fi
|
|
echo "Failed to fetch provisioning data"
|
|
fi
|
|
|
|
if [ -n "$CONFIG_FILE_PATH" ] && [ -f "$CONFIG_FILE_PATH" ]; then
|
|
cp "$CONFIG_FILE_PATH" "$config_file"
|
|
echo "Configuration copied from $CONFIG_FILE_PATH"
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$SERVER_IP" ] || [ -n "$SCREEN_NAME" ] || [ -n "$QUICKCONNECT_KEY" ]; then
|
|
export SERVER_IP SERVER_PORT SCREEN_NAME QUICKCONNECT_KEY
|
|
export ORIENTATION TOUCH_ENABLED MAX_RESOLUTION
|
|
export USE_HTTPS VERIFY_SSL EDIT_ENABLED
|
|
|
|
python3 << PYEOF
|
|
import json, os
|
|
|
|
config_file = "$config_file"
|
|
config = {}
|
|
if os.path.exists(config_file):
|
|
with open(config_file) as f:
|
|
config = json.load(f)
|
|
|
|
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', ''),
|
|
}
|
|
|
|
for key, val in cli_params.items():
|
|
if val:
|
|
config[key] = val
|
|
|
|
https_val = os.environ.get('USE_HTTPS', '')
|
|
verify_val = os.environ.get('VERIFY_SSL', '')
|
|
edit_val = os.environ.get('EDIT_ENABLED', '')
|
|
|
|
if https_val:
|
|
config['use_https'] = https_val.lower() == 'true'
|
|
if verify_val:
|
|
# VERIFY_SSL holds the requested verification state ("true"/"false").
|
|
# (Previously this was inverted: '--no-verify-ssl' set verify_ssl=True.)
|
|
config['verify_ssl'] = verify_val.lower() != 'false'
|
|
if edit_val:
|
|
config['edit_feature_enabled'] = edit_val.lower() == 'true'
|
|
|
|
config.setdefault('port', '8080')
|
|
config.setdefault('orientation', 'Landscape')
|
|
config.setdefault('touch', 'True')
|
|
config.setdefault('max_resolution', '1920x1080')
|
|
config.setdefault('edit_feature_enabled', True)
|
|
config.setdefault('use_https', False)
|
|
config.setdefault('verify_ssl', False)
|
|
|
|
with open(config_file, 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
|
|
print("Configuration written")
|
|
PYEOF
|
|
elif [ ! -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": true,
|
|
"use_https": false,
|
|
"verify_ssl": false
|
|
}
|
|
EOF
|
|
echo "Default config template created"
|
|
fi
|
|
}
|
|
write_player_config
|
|
|
|
# Initialize authentication
|
|
config_file="$SCRIPT_DIR/config/app_config.json"
|
|
if [ -f "$config_file" ]; then
|
|
if python3 -c "
|
|
import json
|
|
c = json.load(open('$config_file'))
|
|
if c.get('server_ip') and c.get('screen_name') and c.get('quickconnect_key'):
|
|
exit(0)
|
|
else:
|
|
exit(1)
|
|
" 2>/dev/null; then
|
|
echo "Attempting player registration..."
|
|
cd "$SCRIPT_DIR/src"
|
|
source "$VENV_DIR/bin/activate"
|
|
python3 -c "
|
|
import json, sys
|
|
sys.path.insert(0, '.')
|
|
from get_playlists_v2 import ensure_authenticated
|
|
|
|
with open('$config_file') as f:
|
|
config = json.load(f)
|
|
|
|
auth = ensure_authenticated(config)
|
|
if auth:
|
|
print('Player registered successfully')
|
|
sys.exit(0)
|
|
else:
|
|
print('Registration deferred')
|
|
sys.exit(1)
|
|
" 2>&1 || echo "Auth initialization deferred"
|
|
deactivate 2>/dev/null || true
|
|
cd "$SCRIPT_DIR"
|
|
else
|
|
echo "Incomplete config - auth will happen on first run"
|
|
fi
|
|
fi
|
|
|
|
# Create background runner
|
|
NOHUP_WRAPPER="$SCRIPT_DIR/.run-background.sh"
|
|
cat > "$NOHUP_WRAPPER" << 'EOF'
|
|
#!/bin/bash
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
LOG_FILE="$SCRIPT_DIR/logs/player-background.log"
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting player in background..." >> "$LOG_FILE"
|
|
cd "$SCRIPT_DIR" || exit 1
|
|
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"
|
|
|
|
# ============================================================
|
|
# Final Summary
|
|
# ============================================================
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "Installation Complete!"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Installation: $SCRIPT_DIR"
|
|
echo "Virtual Env: $VENV_DIR"
|
|
echo ""
|
|
|
|
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"
|
|
echo " bash check_player_status.sh Check player status"
|
|
echo " bash stop_player.sh Stop the player"
|
|
echo ""
|
|
echo "Autostart enabled for user: $ACTUAL_USER"
|
|
echo ""
|
|
echo "Logs: $SCRIPT_DIR/logs/"
|
|
echo ""
|
|
|
|
if [ -f "$PI_BOOT_PATCH_SCRIPT" ]; then
|
|
echo "Applying one-time Raspberry Pi boot config patch..."
|
|
run_sudo chmod +x "$PI_BOOT_PATCH_SCRIPT" 2>/dev/null || true
|
|
if run_sudo bash "$PI_BOOT_PATCH_SCRIPT"; then
|
|
echo "One-time boot patch applied (or already present)"
|
|
else
|
|
echo "Boot patch skipped or failed; continuing installation"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
if [ "$NO_REBOOT" = true ]; then
|
|
echo "=========================================="
|
|
echo "Reboot Skipped (--no-reboot)"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Some changes (HDMI settings, GPU memory) may require a reboot."
|
|
echo "Reboot manually when convenient: sudo reboot"
|
|
echo ""
|
|
elif [ -z "$SUDO_PASSWORD" ]; then
|
|
echo "=========================================="
|
|
echo "System Reboot Recommended"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Some changes (HDMI settings, GPU memory) require a reboot."
|
|
echo ""
|
|
echo "Rebooting in 5 seconds... Press Ctrl+C to cancel."
|
|
sleep 5
|
|
echo "Rebooting now..."
|
|
run_sudo reboot
|
|
fi
|