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
-79
View File
@@ -1,79 +0,0 @@
#!/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
+36 -12
View File
@@ -30,24 +30,48 @@ env/
*.pyc
# Media files (optional - remove if you want to track media)
media/*.jpg
media/*.jpeg
media/*.png
media/*.gif
media/*.bmp
media/*.mp4
media/*.avi
media/*.mkv
media/*.mov
media/*.webm
media/**/*.jpg
media/**/*.jpeg
media/**/*.png
media/**/*.gif
media/**/*.bmp
media/**/*.mp4
media/**/*.avi
media/**/*.mkv
media/**/*.mov
media/**/*.webm
# Playlists cache (auto-generated)
playlists/server_playlist_*.json
# Downloaded / on-device edited media (server-managed runtime content)
media/edited_media/
# Playlists cache (auto-generated by the player)
playlists/server_playlist*.json
# Player configuration (machine-specific; install.sh writes a template if absent)
config/app_config.json
# Logs
*.log
.kivy/
# Runtime state (generated by the player / watchdog)
.player_heartbeat
.player_stop_requested
.player_watchdog.lock
.post-clone-setup.done
.post-clone-backups/
# Installer-generated launchers (recreated by install.sh)
.run-background.sh
.start-player-cron.sh
# Secrets / machine-specific auth tokens (never commit)
player_auth.json
# Local backups / editor leftovers
*.bak
*~
# IDE
.vscode/
.idea/
-54
View File
@@ -1,54 +0,0 @@
#!/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
-11
View File
@@ -1,11 +0,0 @@
#\!/bin/bash
SCRIPT_DIR="/home/pi/kiwy-signage"
LOG_FILE="/home/pi/kiwy-signage/logs/player-background.log"
mkdir -p "."
echo "[2026-07-17 10:54:53] Starting player in background..." >> ""
cd "/home/pi/kiwy-signage" || exit 1
nohup bash start.sh >> "" 2>&1 &
PLAYER_PID=$\!
echo "[2026-07-17 10:54:53] Player started with PID: $PLAYER_PID" >> ""
echo "Player started in background (PID: $PLAYER_PID)"
echo "Logs: $LOG_FILE"
-3
View File
@@ -1,3 +0,0 @@
#\!/bin/bash
sleep 15
cd /home/pi/kiwy-signage && bash start.sh
-54
View File
@@ -1,54 +0,0 @@
#!/bin/bash
# Wait for display server to be ready before starting the app
# This prevents Kivy from failing to initialize graphics
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MAX_WAIT=60
ELAPSED=0
echo "[$(date)] Waiting for display server to be ready..."
# Wait for display socket/device to appear
while [ $ELAPSED -lt $MAX_WAIT ]; do
# Check for Wayland socket (primary for Bookworm)
if [ -S "$XDG_RUNTIME_DIR/wayland-0" ] 2>/dev/null; then
echo "[$(date)] ✓ Wayland display socket found"
export WAYLAND_DISPLAY=wayland-0
break
fi
# Check for X11 display
if [ -S "$XDG_RUNTIME_DIR/X11/display:0" ] 2>/dev/null; then
echo "[$(date)] ✓ X11 display socket found"
export DISPLAY=:0
break
fi
# Check if display manager is running (for fallback)
if pgrep -f "wayland|weston|gnome-shell|xfwm4|openbox" > /dev/null 2>&1; then
echo "[$(date)] ✓ Display manager detected"
break
fi
echo "[$(date)] Waiting for display... ($ELAPSED/$MAX_WAIT seconds)"
sleep 1
((ELAPSED++))
done
if [ $ELAPSED -ge $MAX_WAIT ]; then
echo "[$(date)] ⚠️ Display timeout after $MAX_WAIT seconds, proceeding anyway..."
fi
# Set default display if not detected
if [ -z "$WAYLAND_DISPLAY" ] && [ -z "$DISPLAY" ]; then
echo "[$(date)] Using fallback display settings"
export DISPLAY=:0
export WAYLAND_DISPLAY=wayland-0
fi
echo "[$(date)] Environment: DISPLAY=$DISPLAY WAYLAND_DISPLAY=$WAYLAND_DISPLAY"
echo "[$(date)] XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
# Now start the app
cd "$SCRIPT_DIR" || exit 1
exec bash start.sh
-43
View File
@@ -1,43 +0,0 @@
INFO ] [Kivy ] Installed at "/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/kivy/__init__.py"
[INFO ] [Python ] v3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0]
[INFO ] [Python ] Interpreter at "/home/pi/Desktop/Kiwy-Signage/.venv/bin/python3"
[INFO ] [Logger ] Purge log fired. Processing...
[INFO ] [Logger ] Purge finished!
[DEBUG ] [Using selector] EpollSelector
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
[ERROR ] [Image ] Error loading </home/pi/Desktop/Kiwy-Signage/config/resources/intro1.mp4>
[WARNING] ⚠️ SSL verification disabled - NOT recommended for production!
[DEBUG ] [Starting new HTTPS connection (1)] 192.168.0.121:443
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None
[INFO ] ✅ Auth code verified
[INFO ] ✅ Using existing authentication
[INFO ] [Fetching playlist from] https://192.168.0.121:443/api/playlists/1
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[DEBUG ] [https ]//192.168.0.121:443 "GET /api/playlists/1 HTTP/1.1" 200 None
[INFO ] [✅ Playlist received (version] 34)
[INFO ] [📊 Playlist versions - Server] v34, Local: v34
[INFO ] ✓ Playlist is up to date
[WARNING] Deprecated property "<BooleanProperty name=allow_stretch>" of object "<kivy.uix.image.AsyncImage object at 0x7fa5f79ef0>" has been set, it will be removed in a future version
[WARNING] Deprecated property "<BooleanProperty name=keep_ratio>" of object "<kivy.uix.image.AsyncImage object at 0x7fa5f79ef0>" was accessed, it will be removed in a future version
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None
[INFO ] ✅ Auth code verified
[INFO ] ✅ Using existing authentication
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/player-feedback HTTP/1.1" 200 None
^C[2026-01-17 22:09:12] 🛑 Watchdog received stop signal
pi@rpi-tvcanba1:~/Desktop/Kiwy-Signage $
-12
View File
@@ -1,12 +0,0 @@
{
"server_ip": "192.168.0.109",
"port": "8080",
"screen_name": "Birou_IT",
"quickconnect_key": "8887779",
"orientation": "Landscape",
"touch": "True",
"max_resolution": "1920x1080",
"edit_feature_enabled": true,
"use_https": false,
"verify_ssl": false
}
@@ -75,7 +75,7 @@ If no card is swiped (timeout), `user_card_data` will be `null`.
A test utility is provided to verify card reader functionality:
```bash
cd /home/pi/Desktop/Kiwy-Signage/working_files
cd /home/pi/Desktop/Kiwy-Signage/dev/tests
python3 test_card_reader.py
```
@@ -178,5 +178,5 @@ Potential improvements for the card reader system:
- `/src/main.py` - Main implementation (CardReader class, authentication flow)
- `/src/edit_drowing.py` - Drawing/editing interface (uses authenticated user)
- `/working_files/test_card_reader.py` - Card reader test utility
- `/dev/tests/test_card_reader.py` - Card reader test utility
- `/requirements.txt` - Dependencies (includes evdev)
@@ -54,7 +54,7 @@ The system now logs:
Run the enhanced test script to identify your card reader:
```bash
cd /home/pi/Desktop/Kiwy-Signage/working_files
cd /home/pi/Desktop/Kiwy-Signage/dev/tests
python3 test_card_reader.py
```
@@ -147,7 +147,7 @@ sudo usermod -a -G input $USER
- Implemented priority-based search
- Enhanced logging
2. **working_files/test_card_reader.py**
2. **dev/tests/test_card_reader.py**
- Updated `list_input_devices()` to show device classifications
- Updated `test_card_reader()` to use same logic as main app
- Added visual indicators for device types
@@ -16,8 +16,9 @@ Kiwy-Signage/
│ │ ├── apt-packages.txt # List of required apt packages
│ │ └── debs/ # Downloaded .deb files (optional)
│ └── README.md
├── download_offline_packages.sh # Download Python packages
├── download_deb_packages.sh # Download system .deb packages
├── scripts/
│ ├── download_offline_packages.sh # Download Python packages
│ └── download_deb_packages.sh # Download system .deb packages
└── install.sh # Smart installer (online/offline)
```
@@ -33,10 +34,10 @@ git clone <repository-url>
cd Kiwy-Signage
# Download Python packages
bash download_offline_packages.sh
bash scripts/download_offline_packages.sh
# (Optional) Download system .deb packages
bash download_deb_packages.sh
bash scripts/download_deb_packages.sh
```
This will populate the `repo/` folder with all necessary packages.
@@ -104,7 +105,7 @@ To update the offline package cache:
```bash
# On a connected system
bash download_offline_packages.sh
bash scripts/download_offline_packages.sh
```
This will download the latest versions of all packages.
@@ -117,7 +118,7 @@ If installation fails due to missing dependencies:
```bash
# Download .deb packages with dependencies
bash download_deb_packages.sh
bash scripts/download_deb_packages.sh
# Install with dependency resolution
sudo apt install -f
@@ -4,10 +4,10 @@
```bash
# 1. Download Python packages (required)
bash download_offline_packages.sh
bash scripts/download_offline_packages.sh
# 2. Download system .deb packages (optional, for fully offline)
bash download_deb_packages.sh
bash scripts/download_deb_packages.sh
```
## For Offline System (Installation)
@@ -22,12 +22,13 @@ bash install.sh --offline
## What's Included
### Python Packages (18 wheels)
✅ Kivy 2.1.0
✅ Requests 2.32.4
### Python Packages
✅ Kivy 2.3.1
✅ Requests 2.34.2
✅ Bcrypt 4.2.1
✅ Aiohttp 3.9.1 (async HTTP)
✅ Asyncio 3.4.3 (async framework)
✅ Aiohttp 3.11.16 (async HTTP)
✅ ffpyplayer 4.5.3 (video)
✅ evdev 1.9.1 (card reader)
✅ All dependencies
### System Packages
+160 -46
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)"
if [ "$HAS_INTERNET" = true ]; then
echo " Falling back to online installation (requires internet)..."
echo ""
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 "=========================================="
-1056
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

-37
View File
@@ -1,37 +0,0 @@
{
"count": 4,
"player_id": 1,
"player_name": "Test_player1",
"playlist": [
{
"file_name": "sean-oulashin-KMn4VEeEPR8-unsplash.jpg",
"type": "image",
"url": "media/sean-oulashin-KMn4VEeEPR8-unsplash.jpg",
"duration": 14,
"edit_on_player": true
},
{
"file_name": "weblink-1e4a4d6d885a",
"type": "weblink",
"url": "https://moto-adv.com/",
"duration": 30,
"edit_on_player": false
},
{
"file_name": "sample-30s.mp4",
"type": "video",
"url": "media/sample-30s.mp4",
"duration": 31,
"edit_on_player": false
},
{
"file_name": "edited_media/5/eye_e_v2.jpg",
"type": "image",
"url": "media/edited_media/5/eye_e_v2.jpg",
"duration": 50,
"edit_on_player": true
}
],
"playlist_id": 1,
"playlist_version": 14
}
+10 -6
View File
@@ -1,6 +1,10 @@
# Ignore downloaded packages but keep directory structure
repo/python-wheels/*.whl
repo/python-wheels/*.tar.gz
repo/system-packages/debs/
!repo/python-wheels/.gitkeep
!repo/system-packages/.gitkeep
# Patterns are relative to THIS file (the repo/ folder), so no "repo/" prefix.
# (The previous patterns used a "repo/" prefix and therefore never matched.)
# Downloaded system .deb bundles are large and optional.
# Fetch on demand with: bash scripts/download_deb_packages.sh
# They are intentionally NOT committed.
system-packages/debs/
# NOTE: Python wheels under python-wheels/ ARE committed on purpose, so that a
# plain `git clone` ships a complete offline install bundle.
+10 -8
View File
@@ -13,21 +13,23 @@ The installer script (`install.sh`) will automatically use this repo folder if a
### To populate this folder for offline installation:
Run the download script from the project root:
Run the download scripts from the project root:
```bash
bash download_offline_packages.sh
bash scripts/download_offline_packages.sh
bash scripts/download_deb_packages.sh
```
This will download all necessary Python wheels and document system packages.
This will download all necessary Python wheels and system .deb packages.
## Contents
### Python Packages
- kivy==2.1.0
- requests==2.32.4
### Python Packages (see requirements.txt for the source of truth)
- kivy==2.3.1
- requests==2.34.2
- bcrypt==4.2.1
- aiohttp==3.9.1
- asyncio==3.4.3
- aiohttp==3.11.16
- ffpyplayer==4.5.3
- evdev==1.9.1
- All dependencies
### System Packages (APT)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+347
View File
@@ -0,0 +1,347 @@
#!/bin/bash
#
# run_player-afterclone.sh
# ============================================================
# ONE-TIME post-clone setup for a cloned Raspberry Pi SD card.
#
# Problem it solves:
# The player identifies itself to DigiServer using the "screen_name"
# value in config/app_config.json (NOT the OS hostname). A cloned SD
# card carries the ORIGINAL card's screen_name + a cached auth token
# (src/player_auth.json). Two devices then fight over the same player
# session, so the cloned device never connects.
#
# What this script does:
# 1. Stops the running player/watchdog (so the token is not re-written)
# 2. Backs up the current identity/config files
# 3. Deletes the cached authentication token(s) -> forces re-auth
# 4. Sets a NEW unique screen_name and (optionally) server details
# 5. Leaves the player ready to boot and register as a BRAND-NEW player
#
# Usage:
# bash run_player-afterclone.sh [OPTIONS]
#
# Options:
# -n, --name NAME New unique screen/player name
# -s, --server IP Server IP / hostname
# -p, --port PORT Server port
# -k, --key KEY Quick-connect / auth key
# --https Enable HTTPS
# --no-https Disable HTTPS
# --verify-ssl Enable SSL certificate verification
# --no-verify-ssl Disable SSL certificate verification
# --set-hostname Also set the OS hostname to the new screen name
# -y, --yes Non-interactive; use args/defaults, no prompts
# --force Run even if this card was already set up
# -h, --help Show this help
#
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$SCRIPT_DIR/config"
CONFIG_FILE="$CONFIG_DIR/app_config.json"
VENV_DIR="$SCRIPT_DIR/.venv"
MARKER_FILE="$SCRIPT_DIR/.post-clone-setup.done"
BACKUP_ROOT="$SCRIPT_DIR/.post-clone-backups"
STAMP="$(date '+%Y%m%d-%H%M%S')"
# ---- Defaults / CLI state ----
NEW_NAME=""
NEW_SERVER=""
NEW_PORT=""
NEW_KEY=""
NEW_USE_HTTPS=""
NEW_VERIFY_SSL=""
SET_HOSTNAME=false
ASSUME_YES=false
FORCE=false
# ---- Pretty printing ----
info() { echo " $1"; }
ok() { echo " ✓ $1"; }
warn() { echo " ⚠️ $1"; }
err() { echo " ✗ $1"; }
usage() {
cat <<'EOF'
run_player-afterclone.sh - one-time post-clone setup for a cloned SD card.
Deletes the cached authentication token and configures a NEW unique
player identity so the card registers on the server as a new player.
Usage:
bash run_player-afterclone.sh [OPTIONS]
Options:
-n, --name NAME New unique screen/player name
-s, --server IP Server IP / hostname
-p, --port PORT Server port
-k, --key KEY Quick-connect / auth key
--https Enable HTTPS
--no-https Disable HTTPS
--verify-ssl Enable SSL certificate verification
--no-verify-ssl Disable SSL certificate verification
--set-hostname Also set the OS hostname to the new screen name
-y, --yes Non-interactive; use args/defaults, no prompts
--force Run even if this card was already set up
-h, --help Show this help
EOF
exit 0
}
# ---- Pick a python interpreter (prefer the venv, fall back to system) ----
pick_python() {
if [ -x "$VENV_DIR/bin/python3" ]; then
echo "$VENV_DIR/bin/python3"
elif command -v python3 >/dev/null 2>&1; then
echo "python3"
else
echo ""
fi
}
# ---- Parse arguments ----
while [ $# -gt 0 ]; do
case "$1" in
-n|--name) NEW_NAME="$2"; shift 2 ;;
-s|--server) NEW_SERVER="$2"; shift 2 ;;
-p|--port) NEW_PORT="$2"; shift 2 ;;
-k|--key) NEW_KEY="$2"; shift 2 ;;
--https) NEW_USE_HTTPS="true"; shift ;;
--no-https) NEW_USE_HTTPS="false"; shift ;;
--verify-ssl) NEW_VERIFY_SSL="true"; shift ;;
--no-verify-ssl) NEW_VERIFY_SSL="false"; shift ;;
--set-hostname) SET_HOSTNAME=true; shift ;;
-y|--yes) ASSUME_YES=true; shift ;;
--force) FORCE=true; shift ;;
-h|--help) usage ;;
*) err "Unknown option: $1"; echo ""; usage ;;
esac
done
echo "=========================================="
echo "Kiwy Signage - Post-Clone Setup"
echo "=========================================="
echo ""
# ---- Safety: only run once per card (unless --force) ----
if [ -f "$MARKER_FILE" ] && [ "$FORCE" != true ]; then
warn "This card looks already set up (marker: $(basename "$MARKER_FILE"))."
warn "Re-run with --force if you really want to reset it again."
echo ""
exit 1
fi
PYTHON_BIN="$(pick_python)"
if [ -z "$PYTHON_BIN" ]; then
err "python3 not found. Run install.sh first."
exit 1
fi
# ============================================================
# 1) Stop the running player + watchdog so nothing re-writes the token
# ============================================================
echo "▶ Stopping any running player/watchdog..."
pkill -f "bash.*start.sh" 2>/dev/null || true
pkill -f "python3 main.py" 2>/dev/null || true
sleep 2
pkill -9 -f "bash.*start.sh" 2>/dev/null || true
pkill -9 -f "python3 main.py" 2>/dev/null || true
ok "Player stopped"
echo ""
# ============================================================
# 2) Read current config (for defaults / display)
# ============================================================
CUR_NAME=""
CUR_SERVER=""
CUR_PORT=""
CUR_KEY=""
if [ -f "$CONFIG_FILE" ]; then
eval "$("$PYTHON_BIN" - "$CONFIG_FILE" <<'PYEOF'
import json, sys
try:
c = json.load(open(sys.argv[1]))
except Exception:
c = {}
def q(k):
v = c.get(k, "")
v = "" if v is None else str(v)
return v.replace("'", "'\\''")
print(f"CUR_NAME='{q('screen_name')}'")
print(f"CUR_SERVER='{q('server_ip')}'")
print(f"CUR_PORT='{q('port')}'")
print(f"CUR_KEY='{q('quickconnect_key')}'")
PYEOF
)"
fi
OS_HOSTNAME="$(hostname 2>/dev/null || echo '')"
# Sensible defaults: prefer an explicit new name, else the (changed) OS hostname,
# else the previous screen name.
DEF_NAME="$NEW_NAME"
[ -z "$DEF_NAME" ] && DEF_NAME="$OS_HOSTNAME"
[ -z "$DEF_NAME" ] && DEF_NAME="$CUR_NAME"
[ -z "$DEF_NAME" ] && DEF_NAME="player-$(date +%s | tail -c 5)"
# ============================================================
# 3) Prompt (unless -y / non-interactive) for the new identity
# ============================================================
prompt_value() {
# $1 = prompt text, $2 = default
local answer
if [ "$ASSUME_YES" = true ] || [ ! -t 0 ]; then
echo "$2"
return
fi
read -r -p " $1 [$2]: " answer
echo "${answer:-$2}"
}
echo "▶ New player identity"
info "Note: the server identifies this player by 'screen_name', not the OS hostname."
if [ -z "$NEW_NAME" ]; then
NEW_NAME="$(prompt_value "New unique screen/player name" "$DEF_NAME")"
fi
if [ -z "$NEW_SERVER" ]; then
NEW_SERVER="$(prompt_value "Server IP/hostname" "$CUR_SERVER")"
fi
if [ -z "$NEW_PORT" ]; then
NEW_PORT="$(prompt_value "Server port" "${CUR_PORT:-8080}")"
fi
if [ -z "$NEW_KEY" ]; then
NEW_KEY="$(prompt_value "Quick-connect key" "$CUR_KEY")"
fi
echo ""
# ============================================================
# 4) Back up current identity files
# ============================================================
BACKUP_DIR="$BACKUP_ROOT/$STAMP"
mkdir -p "$BACKUP_DIR"
echo "▶ Backing up current identity files to:"
info "$BACKUP_DIR"
[ -f "$CONFIG_FILE" ] && cp -p "$CONFIG_FILE" "$BACKUP_DIR/" 2>/dev/null || true
[ -f "$SCRIPT_DIR/src/player_auth.json" ] && cp -p "$SCRIPT_DIR/src/player_auth.json" "$BACKUP_DIR/" 2>/dev/null || true
ok "Backup complete"
echo ""
# ============================================================
# 5) Delete cached authentication tokens (force re-authentication)
# ============================================================
echo "▶ Removing cached authentication token(s)..."
REMOVED=0
while IFS= read -r token; do
[ -z "$token" ] && continue
rm -f "$token" && REMOVED=$((REMOVED + 1)) && info "deleted: ${token#$SCRIPT_DIR/}"
done < <(find "$SCRIPT_DIR" -name 'player_auth.json' -not -path '*/.venv/*' -not -path "$BACKUP_ROOT/*" 2>/dev/null)
rm -f /tmp/temp_auth_test.json 2>/dev/null || true
rm -f "$SCRIPT_DIR/.player_heartbeat" 2>/dev/null || true
rm -f "$SCRIPT_DIR/.player_stop_requested" 2>/dev/null || true
if [ "$REMOVED" -eq 0 ]; then
ok "No cached token found (already clean)"
else
ok "Removed $REMOVED token file(s)"
fi
echo ""
# ============================================================
# 6) Write the new config (screen_name + server details)
# ============================================================
echo "▶ Writing new player configuration..."
mkdir -p "$CONFIG_DIR"
CFG_PATH="$CONFIG_FILE" \
NEW_SCREEN_NAME="$NEW_NAME" \
NEW_SERVER_IP="$NEW_SERVER" \
NEW_PORT="$NEW_PORT" \
NEW_KEY="$NEW_KEY" \
NEW_USE_HTTPS="$NEW_USE_HTTPS" \
NEW_VERIFY_SSL="$NEW_VERIFY_SSL" \
"$PYTHON_BIN" <<'PYEOF'
import json, os
cfg_path = os.environ['CFG_PATH']
try:
with open(cfg_path) as f:
cfg = json.load(f)
except Exception:
cfg = {}
updates = {
'screen_name': os.environ.get('NEW_SCREEN_NAME', ''),
'server_ip': os.environ.get('NEW_SERVER_IP', ''),
'port': os.environ.get('NEW_PORT', ''),
'quickconnect_key': os.environ.get('NEW_KEY', ''),
}
for key, val in updates.items():
if val != '':
cfg[key] = val
if os.environ.get('NEW_USE_HTTPS', '') != '':
cfg['use_https'] = os.environ['NEW_USE_HTTPS'].lower() == 'true'
if os.environ.get('NEW_VERIFY_SSL', '') != '':
cfg['verify_ssl'] = os.environ['NEW_VERIFY_SSL'].lower() == 'true'
# Safe defaults for anything missing
cfg.setdefault('port', '8080')
cfg.setdefault('orientation', 'Landscape')
cfg.setdefault('touch', 'True')
cfg.setdefault('max_resolution', '1920x1080')
cfg.setdefault('edit_feature_enabled', True)
cfg.setdefault('use_https', False)
cfg.setdefault('verify_ssl', False)
with open(cfg_path, 'w') as f:
json.dump(cfg, f, indent=2)
f.write('\n')
print(json.dumps(cfg, indent=2))
PYEOF
ok "Configuration updated"
echo ""
# ============================================================
# 7) Optionally align the OS hostname with the screen name
# ============================================================
if [ "$SET_HOSTNAME" = true ] && [ -n "$NEW_NAME" ]; then
SAFE_HOST="$(echo "$NEW_NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed 's/-\+$//')"
echo "▶ Setting OS hostname to '$SAFE_HOST' (player identity is still 'screen_name')..."
if command -v hostnamectl >/dev/null 2>&1; then
sudo hostnamectl set-hostname "$SAFE_HOST" 2>/dev/null \
&& ok "OS hostname set to $SAFE_HOST" \
|| warn "Could not set OS hostname (needs sudo)"
else
echo "$SAFE_HOST" | sudo tee /etc/hostname >/dev/null 2>&1 \
&& ok "OS hostname file updated" \
|| warn "Could not write /etc/hostname (needs sudo)"
fi
warn "A reboot is recommended for the hostname change to fully apply."
echo ""
fi
# ============================================================
# 8) Mark the card as set up + summary
# ============================================================
{
echo "Post-clone setup completed: $(date '+%Y-%m-%d %H:%M:%S')"
echo "screen_name: $NEW_NAME"
echo "server_ip: $NEW_SERVER"
echo "port: $NEW_PORT"
echo "backup: $BACKUP_DIR"
} > "$MARKER_FILE"
echo "=========================================="
echo "✓ Card is ready to connect to the server"
echo "=========================================="
echo ""
echo " Player name : $NEW_NAME"
echo " Server : $NEW_SERVER:$NEW_PORT"
echo " Backup : $BACKUP_DIR"
echo ""
echo " Next steps:"
echo " 1. (Optional) reboot: sudo reboot"
echo " 2. Start the player: ./start.sh"
echo ""
echo " The player will authenticate as a NEW player '$NEW_NAME'"
echo " and fetch its own playlist from the server."
echo ""
+14 -3
View File
@@ -1,5 +1,16 @@
#!/bin/bash
# Start Kivy Signage Player
cd "$(dirname "$0")/src"
python3 main.py
# Run Kivy Signage Player once (no watchdog / auto-restart)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Activate the virtual environment created by install.sh (holds Kivy + deps).
if [ -f "$SCRIPT_DIR/.venv/bin/activate" ]; then
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.venv/bin/activate"
else
echo "WARNING: virtual environment not found at $SCRIPT_DIR/.venv"
echo " Run 'bash install.sh' first for a fully offline-capable setup."
fi
cd "$SCRIPT_DIR/src"
exec python3 main.py
@@ -6,7 +6,8 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SYSTEM_DIR="$SCRIPT_DIR/repo/system-packages"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SYSTEM_DIR="$PROJECT_ROOT/repo/system-packages"
DEB_DIR="$SYSTEM_DIR/debs"
echo "=========================================="
@@ -6,7 +6,8 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$SCRIPT_DIR/repo"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_DIR="$PROJECT_ROOT/repo"
WHEELS_DIR="$REPO_DIR/python-wheels"
SYSTEM_DIR="$REPO_DIR/system-packages"
@@ -26,7 +27,7 @@ fi
# Download Python packages
echo "Step 1: Downloading Python wheels..."
echo "--------------------"
cd "$SCRIPT_DIR"
cd "$PROJECT_ROOT"
# Check if pip is installed
if ! command -v pip3 &> /dev/null; then
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
{
"hostname": "Birou_IT",
"auth_code": "CZncd_2dlTZGieBEdqAbUTjf3qNyEUPDXr8jLVx7NLs",
"player_id": 1,
"player_name": "Test_player1",
"playlist_id": 1,
"orientation": "Landscape",
"authenticated": true,
"server_url": "http://192.168.0.109:8080"
}
+28 -2
View File
@@ -59,6 +59,13 @@ load_user_environment() {
# Fallback: detect display manually
log_message "Falling back to manual display detection..."
# Ensure XDG_RUNTIME_DIR is set. Wayland clients (and Kivy) need it, and a
# cron-launched instance may not have it. Default to the standard location.
if [ -z "$XDG_RUNTIME_DIR" ]; then
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
log_message "Set XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
fi
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
# Try to detect Wayland display
if [ -S "/run/user/$(id -u)/wayland-0" ]; then
@@ -96,8 +103,27 @@ load_user_environment() {
return 0
}
# Load the user environment early
load_user_environment
# Load the user environment early. If there is no usable display, exit WITHOUT
# taking the lock — otherwise a headless instance (e.g. cron before login) would
# block the real desktop-autostart instance from ever running.
if ! load_user_environment; then
log_message "No display server available — exiting so another instance can start"
exit 0
fi
# ============================================================
# Single-instance guard (prevents parallel watchdogs)
# ============================================================
# Only ONE watchdog may run at a time. If XDG autostart, the cron fallback, and
# a manual start all fire, the first one to reach this point wins; every other
# instance exits immediately instead of spawning a second player.
LOCK_FILE="$SCRIPT_DIR/.player_watchdog.lock"
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
log_message "Another watchdog is already running (lock: $LOCK_FILE) — exiting"
exit 0
fi
log_message "Single-instance lock acquired: $LOCK_FILE"
# Function to configure display output to Full HD (1920x1080)
configure_display_resolution() {
+1
View File
@@ -28,6 +28,7 @@ pkill -9 -f "python3 main.py" 2>/dev/null
# Clean up heartbeat and stop flag files
rm -f "$SCRIPT_DIR/.player_heartbeat"
rm -f "$SCRIPT_DIR/.player_stop_requested"
rm -f "$SCRIPT_DIR/.player_watchdog.lock"
echo ""
echo "✓ Player and watchdog stopped"
-10
View File
@@ -1,10 +0,0 @@
{
"hostname": "tv-terasa",
"auth_code": "iiSyZDLWGyqNIxeRt54XYREgvAio11RwwU1_oJev6WI",
"player_id": 1,
"player_name": "TV-acasa 1",
"playlist_id": 1,
"orientation": "Landscape",
"authenticated": true,
"server_url": "http://digi-signage.moto-adv.com"
}