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:
@@ -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
@@ -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/
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -1,3 +0,0 @@
|
||||
#\!/bin/bash
|
||||
sleep 15
|
||||
cd /home/pi/kiwy-signage && bash start.sh
|
||||
@@ -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
|
||||
@@ -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 $
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
+7
-6
@@ -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
|
||||
+163
-49
@@ -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 "=========================================="
|
||||
|
||||
-1056
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 386 KiB |
@@ -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
@@ -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
@@ -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.
BIN
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.
Binary file not shown.
Executable
+347
@@ -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
@@ -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.
@@ -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"
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user