Cleanup repo + harden offline install and watchdog

Repo hygiene:
- Remove dead scripts (.wait-for-display, .display-keepalive, .keep-screen-alive)
- Remove stale backup/scratch (install.sh.bak, New.txt, stale .pyc)
- Stop tracking machine-specific/generated state (config/app_config.json,
  playlists/server_playlist.json, src/player_auth.json, generated launchers)
- Prune duplicate wheel versions (one per package)
- Reorganize working_files -> dev/, dev/tests/, documentation/dev-notes/, scripts/
- Extend .gitignore; fix no-op rules in repo/.gitignore

Offline install (install.sh):
- Non-fatal system-dep step; per-package apt retry; .deb bundle install
- Skip pip self-upgrade offline; guard online fallbacks behind connectivity probe
- Dependency-free fetch_url (curl/wget/urllib); --no-reboot flag
- Fix corrupted literal backslash-bang escapes (shebangs, test -f, banner)
- Idempotent cron install (no more stacked @reboot lines)

Autostart / watchdog:
- start.sh: single-instance flock lock (.player_watchdog.lock) acquired only
  after a display is confirmed, preventing parallel watchdogs
- Set XDG_RUNTIME_DIR when missing; exit early if headless
- run_player.sh: activate .venv; stop_player.sh: clear lock file
This commit is contained in:
SKE087
2026-10-14 23:29:14 +03:00
parent f7b889be11
commit 31807f37bf
60 changed files with 633 additions and 1469 deletions
+163 -49
View File
@@ -1,4 +1,4 @@
#\!/bin/bash
#!/bin/bash
# Kivy Signage Player Installation Script
# Supports both online and offline installation using a Python virtual environment
@@ -77,6 +77,48 @@ detect_raspberry_pi() {
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
# ============================================================
@@ -92,6 +134,7 @@ MAX_RESOLUTION=""
EDIT_ENABLED=""
PROVISION_URL=""
CONFIG_FILE_PATH=""
NO_REBOOT=false
usage() {
echo "Usage: $0 [OPTIONS]"
@@ -119,6 +162,7 @@ usage() {
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
@@ -186,6 +230,10 @@ while [ $# -gt 0 ]; do
OFFLINE_MODE=true
shift
;;
--no-reboot)
NO_REBOOT=true
shift
;;
-q|--quiet)
exec > >(tee -a /tmp/kivy-install.log) 2>&1
shift
@@ -205,6 +253,12 @@ if [ "$OFFLINE_MODE" = false ] && [ -d "$WHEELS_DIR" ] && [ "$(ls -A "$WHEELS_DI
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
# ============================================================
@@ -216,6 +270,11 @@ if [ "$OFFLINE_MODE" = true ]; then
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
@@ -228,27 +287,54 @@ echo "Step 1/6: Installing system dependencies..."
echo "--------------------"
echo "Updating package lists..."
run_sudo apt update
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..."
if [ "$OFFLINE_MODE" = true ] && [ -d "$DEB_DIR" ] && [ "$(ls -A $DEB_DIR/*.deb 2>/dev/null)" ]; then
echo "Installing from offline .deb 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
run_sudo apt-get install -f -y || 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
run_sudo apt install -y \
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
# 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 "System dependencies installed"
echo ""
# ============================================================
@@ -268,8 +354,12 @@ echo "Virtual environment created at $VENV_DIR"
source "$VENV_DIR/bin/activate"
echo "Upgrading pip..."
pip install --upgrade pip --quiet
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 ""
@@ -293,9 +383,13 @@ if [ -f "$REQUIREMENTS_FILE" ]; then
else
echo ""
echo "Offline installation failed (some wheels may be missing or incompatible)"
echo " Falling back to online installation (requires internet)..."
echo ""
pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true
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
@@ -306,16 +400,20 @@ if [ -f "$REQUIREMENTS_FILE" ]; then
echo "Verifying installed packages..."
pip list --format=columns 2>/dev/null | grep -iE "kivy|ffpyplayer" || true
# If kivy is still missing after pip install, try system-wide fallback
python3 -c "import kivy" 2>/dev/null && echo " kivy OK" || {
echo ""
echo " kivy not found in venv — installing system-wide via pip (break-system-packages)..."
pip install kivy --break-system-packages 2>/dev/null || pip install --user kivy 2>/dev/null || true
}
python3 -c "import ffpyplayer" 2>/dev/null && echo " ffpyplayer OK" || {
echo " ffpyplayer not found — installing system-wide..."
pip install ffpyplayer --break-system-packages 2>/dev/null || pip install --user ffpyplayer 2>/dev/null || 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
@@ -358,24 +456,32 @@ run_sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/kivy-signage-player.d
chmod 644 "$AUTOSTART_DIR/kivy-signage-player.desktop" 2>/dev/null || true
echo "XDG autostart entry created"
# Cron fallback
# 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
#!/bin/bash
# Kiwy signage player cron fallback (managed by install.sh)
sleep 15
cd $SCRIPT_DIR && bash start.sh
EOF
chmod +x "$CRON_WRAPPER"
CRON_ENTRY="@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1"
# Use sudo -u which works when running with sudo (no password prompt)
if sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null | grep -q "kivy-signage-player"; then
true
else
(sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null || true; echo "$CRON_ENTRY") | sudo -u "$ACTUAL_USER" crontab - 2>/dev/null || true
fi
echo "Cron fallback configured"
# 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 ""
@@ -442,7 +548,7 @@ write_player_config() {
if [ -n "$PROVISION_URL" ]; then
echo "Fetching provisioning data from: $PROVISION_URL"
local response
response=$(curl -sSf "$PROVISION_URL" 2>&1 || true)
response=$(fetch_url "$PROVISION_URL" 2>/dev/null || true)
if [ -n "$response" ]; then
echo "$response" > "$config_file"
echo "Provisioning data saved"
@@ -509,7 +615,7 @@ with open(config_file, 'w') as f:
print("Configuration written")
PYEOF
elif [ \! -f "$config_file" ]; then
elif [ ! -f "$config_file" ]; then
cat > "$config_file" << 'EOF'
{
"server_ip": "",
@@ -568,18 +674,18 @@ fi
# Create background runner
NOHUP_WRAPPER="$SCRIPT_DIR/.run-background.sh"
cat > "$NOHUP_WRAPPER" << EOF
#\!/bin/bash
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"
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"
@@ -589,7 +695,7 @@ echo "Background runner created"
# ============================================================
echo ""
echo "=========================================="
echo "Installation Complete\!"
echo "Installation Complete!"
echo "=========================================="
echo ""
echo "Installation: $SCRIPT_DIR"
@@ -636,7 +742,15 @@ if [ -f "$PI_BOOT_PATCH_SCRIPT" ]; then
echo ""
fi
if [ -z "$SUDO_PASSWORD" ]; then
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 "=========================================="