#!/usr/bin/env bash export TERM="${TERM:-xterm}" clear || true set -Eeuo pipefail # === PINCABOS STAGE5C15 PACKAGE UPDATE MODE EXPLICIT ONLY START === # IMPORTANT: # - Sans argument: on NE TOUCHE PAS à l'installation engine originale plus bas. # C'est requis pour ISO, bash wget, ou go-pincabos.sh en installation neuve. # - Avec arguments update: on applique seulement le package publié dans latest.json. # Arguments supportés: # --update-webapp | --webapp # --update-engine | --engine # --apply-package webapp|engine pco_stage5c15_pkg_json_get() { local file="$1" local expr="$2" python3 - "$file" "$expr" <<'PYJSON' import json, sys path, expr = sys.argv[1], sys.argv[2] with open(path, "r", encoding="utf-8") as f: data = json.load(f) cur = data for part in expr.strip(".").split("."): if not part: continue if isinstance(cur, dict) and part in cur: cur = cur[part] else: print("") sys.exit(0) print(cur) PYJSON } pco_stage5c15_apply_package_mode() { local mode="$1" local update_base="${PIN_UPDATE_BASE:-${UPDATE_BASE:-https://ins.pincabos.cc/updates}}" local ts_now ts_now="$(date +%Y%m%d-%H%M%S)" local work="/opt/pincabos/tmp/package-update-${ts_now}" local latest="$work/latest.json" local pkg="$work/package.tar.zst" local root="$work/root" local backup="/opt/pincabos/backups/package-update-${mode}-${ts_now}" local log="/opt/pincabos/logs/02-install-engine-package-${mode}-${ts_now}.log" local file="" local sha="" local size="" mkdir -p /opt/pincabos/tmp /opt/pincabos/logs /opt/pincabos/backups "$work" "$root" exec > >(tee -a "$log") 2>&1 printf "${CYAN}────────────────────────────────────────────────────────────────${RESET}\n" printf "${ORANGE} PinCabOS 02-install-engine package mode: %s${RESET}\n" "$mode" printf "${CYAN}────────────────────────────────────────────────────────────────${RESET}\n" echo echo "Update base: $update_base" echo "Work : $work" echo "Log : $log" echo echo "NOTE: mode package explicite seulement. Sans argument, ce script garde l'installation engine originale." echo echo "=== 1) Dépendances minimales ===" apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl jq tar zstd rsync python3 python3-venv python3-pip nginx echo echo "=== 2) Télécharger latest.json ===" curl -fL --connect-timeout 15 --max-time 90 --retry 3 "$update_base/latest.json" -o "$latest" python3 -m json.tool "$latest" | sed -n '1,220p' echo echo "=== 3) Lire metadata package ===" file="$(pco_stage5c15_pkg_json_get "$latest" ".packages.${mode}.file")" sha="$(pco_stage5c15_pkg_json_get "$latest" ".packages.${mode}.sha256")" size="$(pco_stage5c15_pkg_json_get "$latest" ".packages.${mode}.size")" if [ -z "$file" ] || [ -z "$sha" ]; then echo "NOGOOD: package $mode incomplet dans latest.json" exit 1 fi echo "File: $file" echo "SHA : $sha" echo "Size: $size" echo echo "=== 4) Télécharger package ===" curl -fL --connect-timeout 15 --max-time 1800 --retry 3 "$update_base/$file" -o "$pkg" ls -lh "$pkg" echo echo "=== 5) Vérifier SHA256 ===" printf "%s %s\n" "$sha" "$pkg" | sha256sum -c - echo echo "=== 6) Backup ciblé avant extraction ===" mkdir -p "$backup" case "$mode" in webapp) [ -d /opt/pincabos/web ] && rsync -a /opt/pincabos/web/ "$backup/web/" || true [ -f /etc/systemd/system/pincabos-web.service ] && cp -a /etc/systemd/system/pincabos-web.service "$backup/pincabos-web.service" || true [ -f /etc/nginx/sites-available/pincabos-web.conf ] && cp -a /etc/nginx/sites-available/pincabos-web.conf "$backup/pincabos-web.conf" || true ;; engine) for d in /opt/pincabos/install /opt/pincabos/essentials /opt/pincabos/media /opt/pincabos/tools /opt/pincabos/bin /etc/systemd/system /etc/nginx/sites-available; do [ -e "$d" ] || continue mkdir -p "$backup$(dirname "$d")" cp -a "$d" "$backup$d" 2>/dev/null || true done ;; esac echo "Backup: $backup" echo echo "=== 7) Extraction package vers / ===" rm -rf "$root" mkdir -p "$root" tar --use-compress-program=unzstd -xf "$pkg" -C "$root" rsync -a "$root"/ / echo echo "=== 8) Permissions et services ===" chmod +x /opt/pincabos/install/*.sh 2>/dev/null || true chmod +x /opt/pincabos/bin/* 2>/dev/null || true chmod +x /opt/pincabos/tools/* 2>/dev/null || true chmod +x /usr/local/bin/pincabos* 2>/dev/null || true chown -R pinball:pinball /opt/pincabos/web /opt/pincabos/config /opt/pincabos/media 2>/dev/null || true if [ "$mode" = "webapp" ] || [ "$mode" = "engine" ]; then if [ -f /opt/pincabos/web/requirements.txt ]; then echo echo "=== 9) Réparation WebApp venv ===" python3 -m venv /opt/pincabos/web/.venv /opt/pincabos/web/.venv/bin/python -m pip install --upgrade pip wheel setuptools /opt/pincabos/web/.venv/bin/pip install -r /opt/pincabos/web/requirements.txt chown -R pinball:pinball /opt/pincabos/web/.venv 2>/dev/null || true fi fi systemctl daemon-reload || true systemctl enable nginx 2>/dev/null || true systemctl restart nginx 2>/dev/null || true if systemctl list-unit-files | grep -q '^pincabos-web.service'; then systemctl enable pincabos-web.service 2>/dev/null || true systemctl restart pincabos-web.service 2>/dev/null || true fi if systemctl list-unit-files | grep -q '^pincabos-console.service'; then systemctl enable pincabos-console.service 2>/dev/null || true systemctl restart pincabos-console.service 2>/dev/null || true fi if systemctl list-unit-files | grep -q '^pincabos-vpinfe.service'; then systemctl enable pincabos-vpinfe.service 2>/dev/null || true fi echo echo "=== 10) Validation HTTP locale ===" curl -sI http://127.0.0.1/ | sed -n '1,10p' || true curl -sI http://127.0.0.1/admin | sed -n '1,10p' || true curl -sI http://127.0.0.1/pincabos-update | sed -n '1,10p' || true echo printf "${CYAN}────────────────────────────────────────────────────────────────${RESET}\n" printf "${GREEN}Package %s appliqué OK${RESET}\n" "$mode" printf "${ORANGE}Log: %s${RESET}\n" "$log" printf "${CYAN}────────────────────────────────────────────────────────────────${RESET}\n" } case "${1:-}" in --update-webapp|--webapp) pco_stage5c15_apply_package_mode webapp exit 0 ;; --update-engine|--engine) pco_stage5c15_apply_package_mode engine exit 0 ;; --apply-package) shift case "${1:-}" in webapp|engine) pco_stage5c15_apply_package_mode "$1" exit 0 ;; *) echo "Usage: $0 --apply-package webapp|engine" exit 2 ;; esac ;; --package-help) echo "Usage package mode: $0 --update-webapp|--webapp|--update-engine|--engine|--apply-package webapp|engine" echo "Sans argument: installation engine originale complète." exit 0 ;; esac # === PINCABOS STAGE5C15 PACKAGE UPDATE MODE EXPLICIT ONLY END === ORANGE=$'\033[38;5;208m' RED=$'\033[1;31m' CYAN=$'\033[38;5;51m' GREEN=$'\033[1;32m' YELLOW=$'\033[1;33m' WHITE=$'\033[1;37m' GRAY=$'\033[38;5;245m' RESET=$'\033[0m' SCRIPT_NAME="$(basename "$0")" TS="$(date +%Y%m%d-%H%M%S)" UPDATE_BASE="${UPDATE_BASE:-https://ins.pincabos.cc/updates}" VERSION_JSON_URL="${UPDATE_BASE}/version.json" LATEST_JSON_URL="${UPDATE_BASE}/latest.json" VERSION_JSON_LOCAL="/opt/pincabos/config/version.json" LOG_DIR="/opt/pincabos/logs" INSTALL_DIR="/opt/pincabos/install" CONFIG_DIR="/opt/pincabos/config" APPS_DIR="/opt/pincabos/apps" TOOLS_DIR="/opt/pincabos/tools" DOWNLOAD_DIR="/opt/pincabos/download" STATE_DIR="/opt/pincabos/state" ENGINE_STAGE="/opt/pincabos/config/engine-assets-${TS}" ENGINE_ENV="${ENGINE_STAGE}/engine-env.sh" SYNC_MANIFEST="${ENGINE_STAGE}/pincabos-file-sync.tsv" LOG="${LOG_DIR}/02-install-engine-${TS}.log" PCO_CURRENT_STEP="" PCO_STEPS=() PCO_STATUS=() PCO_FINAL_SUMMARY_DONE=0 PCO_VPX_URL="" PCO_VPX_DIR="" PCO_VPX_BIN="" PCO_VPX_INI="" PCO_TABLES_DIR="" PCO_ROMS_DIR="" PCO_VPINFE_URL="" PCO_VPINFE_DIR="" PCO_VPINFE_BIN="" VPINFE_INI="" pco_line() { printf "${CYAN}────────────────────────────────────────────────────────────────${RESET}\n" } pco_split_step() { local raw="${1:-}" PCO_STEP_NUM="**" PCO_STEP_TITLE="$raw" if [[ "$raw" =~ ^([0-9]+)\)\ (.*)$ ]]; then PCO_STEP_NUM="${BASH_REMATCH[1]}" PCO_STEP_TITLE="${BASH_REMATCH[2]}" fi } pco_title() { local raw="$1" pco_split_step "$raw" printf "\n${CYAN}─[%s]─►${ORANGE} %s ${CYAN}◄────${RESET}\n\n" "$PCO_STEP_NUM" "$PCO_STEP_TITLE" } pco_step() { local step="$1" PCO_CURRENT_STEP="$step" PCO_STEPS+=("$step") PCO_STATUS+=("RUNNING") pco_title "$step" } pco_check() { local step="$1" local i pco_split_step "$step" for i in "${!PCO_STEPS[@]}"; do if [ "${PCO_STEPS[$i]}" = "$step" ]; then PCO_STATUS[$i]="OK" fi done printf "${GREEN}─[%s]─► %s ◄──── Check [√]${RESET}\n" "$PCO_STEP_NUM" "$PCO_STEP_TITLE" } pco_fail_current() { local i if [ -n "${PCO_CURRENT_STEP:-}" ]; then for i in "${!PCO_STEPS[@]}"; do if [ "${PCO_STEPS[$i]}" = "$PCO_CURRENT_STEP" ]; then PCO_STATUS[$i]="FAIL" fi done fi } pco_final_summary() { local exit_code="${1:-0}" local i local failed=0 if [ "${PCO_FINAL_SUMMARY_DONE:-0}" -eq 1 ]; then return 0 fi PCO_FINAL_SUMMARY_DONE=1 echo pco_line printf "${ORANGE}Résumé complet des checks PinCabOS - %s${RESET}\n" "$SCRIPT_NAME" pco_line for i in "${!PCO_STEPS[@]}"; do pco_split_step "${PCO_STEPS[$i]}" case "${PCO_STATUS[$i]}" in OK) printf "${GREEN}─[%s]─► %s ◄──── Check [√]${RESET}\n" "$PCO_STEP_NUM" "$PCO_STEP_TITLE" ;; *) failed=1 printf "${RED}─[%s]─► %s ◄──NOGOOD [X]${RESET}\n" "$PCO_STEP_NUM" "$PCO_STEP_TITLE" ;; esac done pco_line if [ "$failed" -eq 0 ] && [ "$exit_code" -eq 0 ]; then printf "${GREEN}Tous les checks sont réussis. PinCabOS engine install OK.${RESET}\n" else printf "${RED}Un ou plusieurs checks ont échoué. Code retour: %s${RESET}\n" "$exit_code" fi pco_line printf "${ORANGE}Version JSON importé :${RESET} %s\n" "${VERSION_JSON_LOCAL:-non défini}" printf "${ORANGE}VPX URL officiel :${RESET} %s\n" "${PCO_VPX_URL:-non défini}" printf "${ORANGE}VPX dir :${RESET} %s\n" "${PCO_VPX_DIR:-non défini}" printf "${ORANGE}VPX bin :${RESET} %s\n" "${PCO_VPX_BIN:-non défini}" printf "${ORANGE}VPX ini :${RESET} %s\n" "${PCO_VPX_INI:-non défini}" printf "${ORANGE}Tables dir :${RESET} %s\n" "${PCO_TABLES_DIR:-non défini}" printf "${ORANGE}ROMs dir :${RESET} %s\n" "${PCO_ROMS_DIR:-non défini}" printf "${ORANGE}DOF dir :${RESET} %s\n" "/opt/pincabos/apps/dof" printf "${ORANGE}VPinFE URL officiel :${RESET} %s\n" "${PCO_VPINFE_URL:-non défini}" printf "${ORANGE}VPinFE dir :${RESET} %s\n" "${PCO_VPINFE_DIR:-non défini}" printf "${ORANGE}VPinFE bin :${RESET} %s\n" "${PCO_VPINFE_BIN:-non défini}" printf "${ORANGE}VPinFE ini template :${RESET} %s\n" "/opt/pincabos/essentials/VPinFEfiles/vpinfe.ini" printf "${ORANGE}VPinFE ini runtime :${RESET} %s\n" "/home/pinball/.config/vpinfe/vpinfe.ini" printf "${ORANGE}Manifest sync PinCabOS :${RESET} %s\n" "${SYNC_MANIFEST:-non défini}" printf "${ORANGE}Download dir :${RESET} %s\n" "${DOWNLOAD_DIR:-non défini}" printf "${ORANGE}Espace utilisé système :${RESET} %s\n" "$(df -h / | awk 'NR==2 {print $3}')" printf "${ORANGE}Espace disponible :${RESET} %s\n" "$(df -h / | awk 'NR==2 {print $4}')" printf "${ORANGE}Utilisation système :${RESET} %s\n" "$(df -h / | awk 'NR==2 {print $5}')" # === PinCabOS managed block: final-summary-listening-ports BEGIN === pco_line printf "${ORANGE}Pause 5 secondes avant affichage des ports a l'ecoute...${RESET}\n" sleep 5 pco_show_listening_ports printf "${ORANGE}Ports reserves VPinFE :${RESET} 8000 / 8001\n" printf "${ORANGE}Port Console PinCabOS :${RESET} 8090\n" printf "${ORANGE}Port WebApp public :${RESET} 80 via Nginx\n" printf "${ORANGE}Port WebApp interne :${RESET} 5055\n" # === PinCabOS managed block: final-summary-listening-ports END === pco_line echo printf "${YELLOW}AVERTISSEMENT:${RESET}\n" printf "${YELLOW}Un reboot est recommandé après 02-install-engine.sh si VPX, VPinFE, audio, joystick ou services graphiques viennent d'être installés.${RESET}\n" pco_line echo if [ -n "${LOG:-}" ] && [ -f "${LOG:-}" ]; then printf "${ORANGE}Rapport local:${RESET} %s\n" "$LOG" echo fi } pco_on_error() { local exit_code="$?" pco_fail_current pco_final_summary "$exit_code" exit "$exit_code" } trap pco_on_error ERR trap 'pco_final_summary "$?"' EXIT pco_spinner_wait() { local pid="$1" local label="$2" local spin='|/-\' local i=0 while kill -0 "$pid" 2>/dev/null; do i=$(( (i + 1) % 4 )) printf "\r${CYAN}[%c]${ORANGE} %s...${RESET}" "${spin:$i:1}" "$label" sleep 0.15 done wait "$pid" local rc="$?" printf "\r%100s\r" " " return "$rc" } run_spin() { local label="$1" shift local tmp="/tmp/pincabos-spin-${RANDOM}-${RANDOM}.log" "$@" >"$tmp" 2>&1 & local pid="$!" if pco_spinner_wait "$pid" "$label"; then cat "$tmp" rm -f "$tmp" return 0 else local rc="$?" cat "$tmp" rm -f "$tmp" return "$rc" fi } run_spin_bash() { local label="$1" shift local tmp="/tmp/pincabos-spin-${RANDOM}-${RANDOM}.log" bash -c "$*" >"$tmp" 2>&1 & local pid="$!" if pco_spinner_wait "$pid" "$label"; then cat "$tmp" rm -f "$tmp" return 0 else local rc="$?" cat "$tmp" rm -f "$tmp" return "$rc" fi } pco_urlencode_path() { python3 - "$1" <<'PYURL' import sys from urllib.parse import quote p = sys.argv[1].strip().lstrip("/") print("/".join(quote(x, safe="") for x in p.split("/"))) PYURL } # === PinCabOS managed block: vpinfe-port-guard BEGIN === pco_show_listening_ports() { echo pco_line printf "${ORANGE}Ports a l'ecoute PinCabOS:${RESET}\n" pco_line if command -v ss >/dev/null 2>&1; then ss -ltnp || true else netstat -ltnp 2>/dev/null || true fi pco_line echo } pco_pid_cmdline() { local pid="${1:-}" if [ -n "$pid" ] && [ -r "/proc/$pid/cmdline" ]; then tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true fi } pco_port_pids() { local port="$1" ss -ltnp 2>/dev/null \ | awk -v p=":${port}" '$4 ~ p"$" {print}' \ | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' \ | sort -u || true } pco_patch_webapp_off_vpinfe_ports() { local web_port="${PINCABOS_WEB_PORT:-5055}" local web_host="${PINCABOS_WEB_HOST:-127.0.0.1}" local console_port="${PINCABOS_CONSOLE_PORT:-8090}" local console_host="${PINCABOS_CONSOLE_HOST:-127.0.0.1}" echo printf "${YELLOW}Correction preventive:${RESET} WebApp/Console ne doivent pas utiliser 8000/8001.\n" mkdir -p /etc/pincabos cat > /etc/pincabos/web.env < /etc/pincabos/console.env < /etc/systemd/system/pincabos-web.service.d/10-pincabos-web-port.conf < /etc/systemd/system/pincabos-console.service.d/10-pincabos-console-port.conf </dev/null || true if [ -f /opt/pincabos/web/app.py ]; then cp -a /opt/pincabos/web/app.py "/opt/pincabos/web/app.py.backup-engine-port-guard-$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true python3 - <<'PYWEB' from pathlib import Path import re p = Path("/opt/pincabos/web/app.py") s = p.read_text(errors="replace") if not re.search(r'^\s*import\s+os\s*$', s, re.M): lines = s.splitlines() insert_at = 0 while insert_at < len(lines) and (lines[insert_at].startswith("#!") or lines[insert_at].strip() == ""): insert_at += 1 lines.insert(insert_at, "import os") s = "\n".join(lines) + "\n" s = re.sub(r'port\s*=\s*8000', 'port=int(os.environ.get("PINCABOS_WEB_PORT", "5055"))', s) s = re.sub(r'port\s*=\s*8001', 'port=int(os.environ.get("PINCABOS_WEB_PORT", "5055"))', s) s = re.sub(r'host\s*=\s*["\']0\.0\.0\.0["\']', 'host=os.environ.get("PINCABOS_WEB_HOST", "127.0.0.1")', s) s = re.sub(r'host\s*=\s*["\']127\.0\.0\.1["\']', 'host=os.environ.get("PINCABOS_WEB_HOST", "127.0.0.1")', s) s = re.sub( r'app\.run\(\s*\)', 'app.run(host=os.environ.get("PINCABOS_WEB_HOST", "127.0.0.1"), port=int(os.environ.get("PINCABOS_WEB_PORT", "5055")))', s ) s = s.replace("127.0.0.1:8000", "127.0.0.1:5055") s = s.replace("localhost:8000", "localhost:5055") s = s.replace("127.0.0.1:8001", "127.0.0.1:5055") s = s.replace("localhost:8001", "localhost:5055") p.write_text(s) PYWEB fi for f in \ /opt/pincabos/console/app.py \ /opt/pincabos/console/server.py \ /opt/pincabos/tools/console-commander.py \ /opt/pincabos/tools/pincabos-console.py \ /opt/pincabos/tools/run-console.sh do if [ -f "$f" ]; then cp -a "$f" "$f.backup-engine-console-port-$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true sed -i \ -e "s/127.0.0.1:8000/127.0.0.1:${console_port}/g" \ -e "s/localhost:8000/localhost:${console_port}/g" \ -e "s/port=8000/port=${console_port}/g" \ -e "s/:8000/:${console_port}/g" \ -e "s/127.0.0.1:8001/127.0.0.1:${console_port}/g" \ -e "s/localhost:8001/localhost:${console_port}/g" \ -e "s/port=8001/port=${console_port}/g" \ -e "s/:8001/:${console_port}/g" \ "$f" || true fi done mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled cat > /etc/nginx/sites-available/pincabos-web.conf </dev/null 2>&1; then nginx -t systemctl restart nginx || true fi if systemctl list-unit-files | grep -q '^pincabos-web.service'; then systemctl restart pincabos-web.service || true fi if systemctl list-unit-files | grep -q '^pincabos-console.service'; then systemctl restart pincabos-console.service || true fi sleep 3 } pco_guard_vpinfe_ports() { local label="${1:-preflight}" local bad_found=0 local port pid cmd echo pco_line printf "${ORANGE}Validation ports VPinFE reserves 8000/8001 - %s${RESET}\n" "$label" pco_line for port in 8000 8001; do for pid in $(pco_port_pids "$port"); do cmd="$(pco_pid_cmdline "$pid")" printf "${YELLOW}Port %s occupe par PID %s:${RESET} %s\n" "$port" "$pid" "$cmd" case "$cmd" in *vpinfe*|*VPinFE*|*/opt/pincabos/apps/frontend/vpinfe/*) printf "${GREEN}OK:${RESET} port %s utilise par VPinFE.\n" "$port" ;; */opt/pincabos/web/app.py*|*pincabos-web*|*console-commander*|*pincabos-console*) printf "${RED}NOGOOD:${RESET} port %s pris par WebApp/Console. Correction automatique.\n" "$port" bad_found=1 ;; *) printf "${RED}NOGOOD:${RESET} port %s pris par un service non VPinFE.\n" "$port" bad_found=1 ;; esac done done if [ "$bad_found" -eq 1 ]; then pco_patch_webapp_off_vpinfe_ports bad_found=0 for port in 8000 8001; do for pid in $(pco_port_pids "$port"); do cmd="$(pco_pid_cmdline "$pid")" case "$cmd" in *vpinfe*|*VPinFE*|*/opt/pincabos/apps/frontend/vpinfe/*) printf "${GREEN}OK apres correction:${RESET} port %s utilise par VPinFE.\n" "$port" ;; *) printf "${RED}NOGOOD apres correction:${RESET} port %s encore pris par PID %s: %s\n" "$port" "$pid" "$cmd" bad_found=1 ;; esac done done fi pco_show_listening_ports if [ "$bad_found" -eq 1 ]; then printf "${RED}ERREUR:${RESET} Les ports 8000/8001 ne sont pas libres pour VPinFE.\n" printf "${RED}02-install-engine.sh arrete ici pour eviter une boucle Errno 98 Address already in use.${RESET}\n" return 1 fi printf "${GREEN}OK:${RESET} ports 8000/8001 libres ou occupes uniquement par VPinFE.\n" pco_line echo return 0 } # === PinCabOS managed block: vpinfe-port-guard END === pco_progress_bar() { local current="${1:-0}" local total="${2:-1}" local label="${3:-Progression}" local width=42 local percent=0 local filled=0 local empty=0 local bar="" local line="" if [ "$total" -gt 0 ]; then percent=$(( current * 100 / total )) filled=$(( current * width / total )) fi if [ "$filled" -lt 0 ]; then filled=0; fi if [ "$filled" -gt "$width" ]; then filled="$width"; fi empty=$(( width - filled )) bar="$(printf '%*s' "$filled" '' | tr ' ' '#')" bar="${bar}$(printf '%*s' "$empty" '' | tr ' ' '-')" line="$(printf "%s [%s] %3s%% %s/%s" "$label" "$bar" "$percent" "$current" "$total")" # Affichage normal dans le log/console courante. printf "\r${CYAN}%s${RESET}" "$line" # Si lancé par systemd/autologin et stdout n'est pas le vrai tty, # afficher aussi directement sur tty1 pour que l'utilisateur voie la progression. if [ -w /dev/tty1 ] && [ ! -t 1 ]; then printf "\r\033[38;5;51m%s\033[0m" "$line" >/dev/tty1 2>/dev/null || true fi # À 100%, forcer une nouvelle ligne propre sur tty1. if [ "$total" -gt 0 ] && [ "$current" -ge "$total" ]; then echo if [ -w /dev/tty1 ] && [ ! -t 1 ]; then echo >/dev/tty1 2>/dev/null || true fi fi } pincabos_header() { clear || true echo printf "${ORANGE}===============================================================${RESET}\n" printf "${ORANGE} PinCabOS - Ultimate VPinball Linux Cabinet System${RESET}\n" printf "${CYAN} VPX Standalone - VPinFE - Engine${RESET}\n" printf "${ORANGE}===============================================================${RESET}\n" echo printf "${GRAY} Script :${RESET} ${WHITE}%s${RESET}\n" "$SCRIPT_NAME" printf "${GRAY} By :${RESET} ${WHITE}Karots Sugarpie${RESET}\n" printf "${GRAY} Hostname :${RESET} %s\n" "$(hostname)" printf "${GRAY} User :${RESET} %s\n" "$(whoami)" pco_line echo } extract_archive() { local archive="$1" local dest="$2" mkdir -p "$dest" case "$archive" in *.zip) unzip -q "$archive" -d "$dest" ;; *.tar.gz|*.tgz) tar -xzf "$archive" -C "$dest" ;; *.tar.xz|*.txz) tar -xJf "$archive" -C "$dest" ;; *.tar.zst|*.tzst) tar --zstd -xf "$archive" -C "$dest" ;; *.AppImage) cp -f "$archive" "$dest/" chmod +x "$dest/$(basename "$archive")" ;; *) echo "ERREUR: format archive non supporte: $archive" exit 1 ;; esac } prepare_archive_payload() { local archive="$1" local workdir="$2" local label="$3" local outer_dir="${workdir}/extract-outer" local inner_dir="${workdir}/extract-inner" local payload_dir="${workdir}/payload-ready" rm -rf "$outer_dir" "$inner_dir" "$payload_dir" mkdir -p "$outer_dir" "$inner_dir" "$payload_dir" echo "Preparation payload $label" >&2 echo "Archive source : $archive" >&2 echo "Workdir : $workdir" >&2 echo >&2 extract_archive "$archive" "$outer_dir" >&2 echo "=== Contenu outer $label ===" >&2 (find "$outer_dir" -maxdepth 3 -type f -printf '%m %s bytes %p\n' 2>/dev/null | sort | sed -n '1,160p' >&2) || true echo >&2 local nested_archive="" nested_archive="$(find "$outer_dir" -type f \ \( -iname "*.zip" -o -iname "*.tar.gz" -o -iname "*.tgz" -o -iname "*.tar.xz" -o -iname "*.txz" -o -iname "*.tar.zst" -o -iname "*.tzst" \) \ | sort | sed -n '1p' || true)" if [ -n "$nested_archive" ]; then echo "Archive interne detectee pour $label:" >&2 echo "$nested_archive" >&2 echo >&2 extract_archive "$nested_archive" "$inner_dir" >&2 local root_dirs root_files only_dir root_dirs="$(find "$inner_dir" -mindepth 1 -maxdepth 1 -type d | wc -l)" root_files="$(find "$inner_dir" -mindepth 1 -maxdepth 1 -type f | wc -l)" if [ "$root_dirs" -eq 1 ] && [ "$root_files" -eq 0 ]; then only_dir="$(find "$inner_dir" -mindepth 1 -maxdepth 1 -type d -print -quit)" rsync -a "$only_dir"/ "$payload_dir"/ else rsync -a "$inner_dir"/ "$payload_dir"/ fi else local root_dirs root_files only_dir root_dirs="$(find "$outer_dir" -mindepth 1 -maxdepth 1 -type d | wc -l)" root_files="$(find "$outer_dir" -mindepth 1 -maxdepth 1 -type f | wc -l)" if [ "$root_dirs" -eq 1 ] && [ "$root_files" -eq 0 ]; then only_dir="$(find "$outer_dir" -mindepth 1 -maxdepth 1 -type d -print -quit)" rsync -a "$only_dir"/ "$payload_dir"/ else rsync -a "$outer_dir"/ "$payload_dir"/ fi fi if [ -z "$(find "$payload_dir" -mindepth 1 -print -quit 2>/dev/null)" ]; then echo "ERREUR: payload vide apres preparation: $label" >&2 exit 1 fi echo "$payload_dir" } find_executable() { local base="$1" shift || true local pattern="" local candidate="" [ -d "$base" ] || return 1 for pattern in "$@"; do while IFS= read -r candidate; do if [ -n "$candidate" ] && [ -f "$candidate" ]; then printf '%s\n' "$candidate" return 0 fi done < <(find "$base" -type f -iname "$pattern" -perm /111 2>/dev/null) done return 1 } github_latest_asset_url() { local repo="$1" local regex="$2" python3 - "$repo" "$regex" <<'PY' import json, re, sys, urllib.request repo = sys.argv[1] regex = re.compile(sys.argv[2], re.I) url = f"https://api.github.com/repos/{repo}/releases/latest" req = urllib.request.Request( url, headers={ "Accept": "application/vnd.github+json", "User-Agent": "PinCabOS-Installer" }, ) with urllib.request.urlopen(req, timeout=60) as r: data = json.load(r) for asset in data.get("assets", []): name = asset.get("name", "") dl = asset.get("browser_download_url", "") if dl and regex.search(name): print(dl) sys.exit(0) print("") PY } # === PINCABOS FIX NGINX CANONICAL WEBAPP START === pco_write_nginx_canonical_webapp() { local web_host="${PINCABOS_WEB_HOST:-${PCO_WEB_HOST:-127.0.0.1}}" local web_port="${PINCABOS_WEB_PORT:-${PCO_WEB_PORT:-5055}}" local console_host="${PINCABOS_CONSOLE_HOST:-127.0.0.1}" local console_port="${PINCABOS_CONSOLE_PORT:-8090}" echo echo "=== PinCabOS - Nginx canonical target config ===" echo "Public Nginx : 0.0.0.0:80" echo "WebApp Python: ${web_host}:${web_port}" echo "Console : ${console_host}:${console_port}" echo "VPinFE : 8000/8001 reserves, pas touche" mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled /opt/pincabos/logs # Sur la machine PinCabOS cible, on garde un seul vhost PinCabOS officiel. # Important: ne pas créer de .disabled dans sites-enabled, Nginx les lit quand même. rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/pincabos-web rm -f /etc/nginx/sites-enabled/pincabos-web.conf find /etc/nginx/sites-enabled -maxdepth 1 -type f -name '*pincabos-web*' -delete 2>/dev/null || true find /etc/nginx/sites-enabled -maxdepth 1 -type l -name '*pincabos-web*' -delete 2>/dev/null || true cat > /etc/nginx/sites-available/pincabos-web.conf < >(tee -a "$LOG") 2>&1 pincabos_header pco_guard_vpinfe_ports "debut 02-install-engine apres init log" pco_step "1) Extraction package engine PinCabOS depuis latest.json" PIN_UPDATE_BASE="${PIN_UPDATE_BASE:-https://ins.pincabos.cc/updates}" PIN_STAGE="/opt/pincabos/stage/pincabos-engine-package-${TS}" PIN_ROOT="${PIN_STAGE}/root" PIN_LATEST="${PIN_STAGE}/latest.json" PIN_PACKAGE="${PIN_STAGE}/pincabos-engine-latest.tar.zst" TREE_DETAIL_LOG="/opt/pincabos/logs/02-install-engine-package-bootstrap-${TS}.detail.log" mkdir -p "$PIN_STAGE" "$PIN_ROOT" /opt/pincabos/logs /opt/pincabos/state /opt/pincabos/backups : > "$TREE_DETAIL_LOG" echo "Source update : $PIN_UPDATE_BASE" echo "Mode : package engine bootstrap + installation complète originale" echo "Detail log : $TREE_DETAIL_LOG" if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1 || ! command -v zstd >/dev/null 2>&1 || ! command -v rsync >/dev/null 2>&1; then echo "Installation dépendances minimales package..." DEBIAN_FRONTEND=noninteractive apt-get update || true DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl jq tar zstd rsync python3 || true fi echo echo "=== 1A) Télécharger latest.json ===" run_spin "Téléchargement latest.json PinCabOS" curl -fsSL "${PIN_UPDATE_BASE}/latest.json" -o "$PIN_LATEST" python3 -m json.tool "$PIN_LATEST" >/dev/null PIN_FILE="$(jq -r '.packages.engine.file // empty' "$PIN_LATEST")" PIN_SHA="$(jq -r '.packages.engine.sha256 // empty' "$PIN_LATEST")" if [ -z "$PIN_FILE" ] || [ -z "$PIN_SHA" ]; then echo "ERREUR: package engine absent/incomplet dans latest.json" exit 1 fi echo "Package engine : $PIN_FILE" echo "SHA attendu : $PIN_SHA" echo echo "=== 1B) Télécharger package engine ===" run_spin "Téléchargement package engine" curl -fL "${PIN_UPDATE_BASE}/${PIN_FILE}" -o "$PIN_PACKAGE" ls -lh "$PIN_PACKAGE" echo echo "=== 1C) Vérifier SHA256 ===" printf "%s %s\n" "$PIN_SHA" "$PIN_PACKAGE" | sha256sum -c - echo echo "=== 1D) Extraction package engine dans staging ===" tar --use-compress-program=unzstd -xf "$PIN_PACKAGE" -C "$PIN_ROOT" echo echo "=== 1E) Application staging vers / sans écraser 02 en cours ===" rsync -a \ --exclude='opt/pincabos/install/02-install-engine.sh' \ --exclude='opt/pincabos/logs/' \ --exclude='opt/pincabos/download/' \ --exclude='opt/pincabos/stage/' \ "$PIN_ROOT"/ / chmod +x /opt/pincabos/bin/*.sh 2>/dev/null || true chmod +x /opt/pincabos/tools/*.sh 2>/dev/null || true chmod +x /opt/pincabos/install/*.sh 2>/dev/null || true chown -R pinball:pinball /opt/pincabos/web /opt/pincabos/config 2>/dev/null || true printf "%s\n" "$PIN_UPDATE_BASE" > /opt/pincabos/state/last-package-update-base.url printf "%s\n" "$PIN_ROOT" > /opt/pincabos/state/last-package-stage-root.path printf "%s\n" "$TREE_DETAIL_LOG" > /opt/pincabos/state/last-package-detail-log.path pco_check "1) Extraction package engine PinCabOS depuis latest.json" pco_step "2) Import version.json officiel PinCabOS" echo "Source officielle:" echo "$VERSION_JSON_URL" echo "Fallback:" echo "$LATEST_JSON_URL" echo echo "Destination locale obligatoire:" echo "$VERSION_JSON_LOCAL" echo mkdir -p "$(dirname "$VERSION_JSON_LOCAL")" "$ENGINE_STAGE" if ! command -v curl >/dev/null 2>&1; then echo "ERREUR: curl absent. Lance d'abord 01-install-system.sh." exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "ERREUR: python3 absent. Lance d'abord 01-install-system.sh." exit 1 fi if [ -s "$VERSION_JSON_LOCAL" ]; then cp -f "$VERSION_JSON_LOCAL" "${ENGINE_STAGE}/version.json.backup-before-import-${TS}" fi if ! run_spin "Telechargement version.json officiel" curl -fL "$VERSION_JSON_URL" -o "${ENGINE_STAGE}/version.json.download"; then echo "AVERTISSEMENT: version.json absent sur le serveur update." echo "Fallback: latest.json sera utilisé comme metadata locale." run_spin "Telechargement latest.json officiel" curl -fL "$LATEST_JSON_URL" -o "${ENGINE_STAGE}/version.json.download" fi if [ ! -s "${ENGINE_STAGE}/version.json.download" ]; then echo "ERREUR: metadata update téléchargée vide ou absente." exit 1 fi python3 -m json.tool "${ENGINE_STAGE}/version.json.download" >/dev/null cp -f "${ENGINE_STAGE}/version.json.download" "$VERSION_JSON_LOCAL" chmod 644 "$VERSION_JSON_LOCAL" python3 -m json.tool "$VERSION_JSON_LOCAL" >/dev/null cp -f "$VERSION_JSON_LOCAL" "${ENGINE_STAGE}/version.json.loaded" echo "Check [OK] version.json officiel importe et local ecrase." python3 -m json.tool "$VERSION_JSON_LOCAL" | sed -n '1,240p' pco_check "2) Import version.json officiel PinCabOS" pco_step "3) Extraction parametres depuis version.json local" python3 - "$VERSION_JSON_LOCAL" "$UPDATE_BASE" "$ENGINE_ENV" "$SYNC_MANIFEST" <<'PY' import json, os, shlex, sys version_json, update_base, engine_env, sync_manifest = sys.argv[1:] update_base = update_base.rstrip("/") data = json.load(open(version_json, "r", encoding="utf-8")) def get_path(*paths): for path in paths: cur = data ok = True for part in path: if isinstance(cur, dict) and part in cur: cur = cur[part] else: ok = False break if ok and isinstance(cur, str) and cur.strip(): return cur.strip() return "" def emit(f, name, value): f.write(f'export {name}={shlex.quote(value)}\n') vpx_dir = get_path(("paths", "vpx"), ("paths", "vpx_dir"), ("vpx", "dir"), ("defaults", "vpx_dir")) or "/opt/pincabos/apps/vpinball" vpinfe_dir = get_path(("pincabos_manifest", "official_vpinfe_paths", "current"), ("paths", "vpinfe"), ("vpinfe", "dir"), ("defaults", "vpinfe_dir")) or "/opt/pincabos/apps/frontend/vpinfe/current" tables_dir = get_path(("tables_directory",), ("paths", "tables"), ("paths", "tables_dir"), ("vpinfe", "tablerootdir"), ("defaults", "tables_dir")) or "/home/pinball/Tables" vpx_ini = get_path(("paths", "vpx_ini"), ("paths", "vpxinipath"), ("vpinfe", "vpxinipath"), ("defaults", "vpx_ini")) or "/home/pinball/.vpinball/VPinballX.ini" roms_dir = get_path(("paths", "roms"), ("paths", "roms_dir"), ("defaults", "roms_dir")) or os.path.join(vpx_dir, "PinMAME", "roms") with open(engine_env, "w", encoding="utf-8") as f: emit(f, "PCO_VPX_DIR", vpx_dir) emit(f, "PCO_VPINFE_DIR", vpinfe_dir) emit(f, "PCO_TABLES_DIR", tables_dir) emit(f, "PCO_VPX_INI", vpx_ini) emit(f, "PCO_ROMS_DIR", roms_dir) open(sync_manifest, "w", encoding="utf-8").close() PY source "$ENGINE_ENV" # === PinCabOS managed block: official-tables-dir BEGIN === # Tables utilisateur officielles PinCabOS. # Les tables ne doivent pas vivre dans /opt/pincabos/apps/vpinball. PCO_TABLES_DIR="/home/pinball/Tables" export PCO_TABLES_DIR mkdir -p "$PCO_TABLES_DIR" chown -R pinball:pinball "$PCO_TABLES_DIR" 2>/dev/null || true chmod 775 "$PCO_TABLES_DIR" 2>/dev/null || true # Si un ancien dossier Tables existe dans l'app VPX, le garder en compat symlink si possible. if [ -d "/opt/pincabos/apps/vpinball/Tables" ] && [ ! -L "/opt/pincabos/apps/vpinball/Tables" ]; then if [ -z "$(find /opt/pincabos/apps/vpinball/Tables -mindepth 1 -print -quit 2>/dev/null)" ]; then rmdir /opt/pincabos/apps/vpinball/Tables 2>/dev/null || true fi fi if [ ! -e "/opt/pincabos/apps/vpinball/Tables" ]; then ln -sfn "$PCO_TABLES_DIR" "/opt/pincabos/apps/vpinball/Tables" 2>/dev/null || true fi # === PinCabOS managed block: official-tables-dir END === cat "$ENGINE_ENV" pco_check "3) Extraction parametres depuis version.json local" pco_step "4) Installation dependances systeme engine" run_spin_bash "Installation dependances VPX/VPinFE" 'DEBIAN_FRONTEND=noninteractive apt-get install -y \ ca-certificates curl wget jq unzip zip tar zstd xz-utils p7zip-full git rsync file \ desktop-file-utils python3 python3-pip python3-venv python3-requests python3-yaml \ python3-psutil python3-flask nginx libgl1 libglu1-mesa libegl1 libgles2 \ libx11-6 libxext6 libxrandr2 libxinerama1 libxcursor1 libxi6 libxfixes3 \ libxxf86vm1 libfontconfig1 libfreetype6 libnss3 libasound2t64 libpulse0 \ libopenal1 libcurl4t64 libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0 \ libsdl2-ttf-2.0-0 libgtk-3-0 libnotify4 libayatana-appindicator3-1 libgbm1 \ libdrm2 libudev1 udev alsa-utils pulseaudio-utils joystick jstest-gtk evtest xvfb' pco_check "4) Installation dependances systeme engine" # === PinCabOS managed block: dof-dependencies BEGIN === pco_step "4B) Installation dépendances DOF / toys / hardware cabinet" echo "Préparation dépendances DOF pour VPX Standalone:" echo "- USB / HID / HIDRAW" echo "- Serial / FTDI / Pinscape / KL25Z / LedWiz-like" echo "- SDL/OpenAL/curl/ssl/boost/tinyxml2/json" echo "- groupes et règles udev PinCabOS" mkdir -p /opt/pincabos/apps/dof /opt/pincabos/logs/dof chown -R pinball:pinball /opt/pincabos/apps/dof /opt/pincabos/logs/dof 2>/dev/null || true for grp in audio video input dialout plugdev tty; do if getent group "$grp" >/dev/null 2>&1; then usermod -aG "$grp" pinball 2>/dev/null || true fi done DOF_PKGS_CANDIDATES=( build-essential cmake make gcc g++ pkg-config git meson ninja-build autoconf automake libtool udev acl libusb-1.0-0 libusb-1.0-0-dev libudev1 libudev-dev libhidapi-hidraw0 libhidapi-libusb0 libhidapi-dev libftdi1-2 libftdi1-dev libftdi-dev libserialport0 libserialport-dev libevdev2 libevdev-dev joystick jstest-gtk evtest libopenal1 libopenal-dev libpulse0 pulseaudio-utils alsa-utils libcurl4t64 libcurl4 libcurl4-openssl-dev libssl-dev zlib1g-dev libbz2-dev libtinyxml2-dev nlohmann-json3-dev libboost-dev libboost-system-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libsdl2-2.0-0 libsdl2-dev libsdl2-image-2.0-0 libsdl2-image-dev libsdl2-mixer-2.0-0 libsdl2-mixer-dev libsdl2-ttf-2.0-0 libsdl2-ttf-dev # GPIO lib runtime: # - Debian 12/Bookworm: libgpiod2 # - Debian 13/Trixie : libgpiod3 # apt-cache show filtre ensuite ce qui existe réellement. libgpiod3 libgpiod2 gpiod libgpiod-dev python3-dev python3-setuptools ) DOF_PKGS_AVAILABLE=() for pkg in "${DOF_PKGS_CANDIDATES[@]}"; do if apt-cache show "$pkg" >/dev/null 2>&1; then DOF_PKGS_AVAILABLE+=("$pkg") else echo "SKIP paquet absent dans APT: $pkg" fi done if [ "${#DOF_PKGS_AVAILABLE[@]}" -gt 0 ]; then run_spin_bash "Installation dépendances DOF disponibles" \ "DEBIAN_FRONTEND=noninteractive apt-get install -y ${DOF_PKGS_AVAILABLE[*]}" else echo "AVERTISSEMENT: aucun paquet DOF candidat disponible via APT." fi cat > /etc/udev/rules.d/99-pincabos-dof.rules <<'EOF_UDEV_DOF' # PinCabOS DOF / cabinet hardware access # HIDRAW devices: LedWiz-like, Pinscape/KL25Z, PACLed, generic cabinet controllers KERNEL=="hidraw*", GROUP="plugdev", MODE="0660" # USB devices usable by DOF/cabinet controllers SUBSYSTEM=="usb", GROUP="plugdev", MODE="0660" # Serial devices: Arduinos, Pinscape serial, KL25Z serial, relay boards KERNEL=="ttyACM[0-9]*", GROUP="dialout", MODE="0660" KERNEL=="ttyUSB[0-9]*", GROUP="dialout", MODE="0660" KERNEL=="ttyS[0-9]*", GROUP="dialout", MODE="0660" # Input devices for cabinet buttons/plunger/encoders SUBSYSTEM=="input", GROUP="input", MODE="0660" EOF_UDEV_DOF udevadm control --reload-rules 2>/dev/null || true udevadm trigger 2>/dev/null || true echo echo "=== Validation dépendances DOF runtime/build ===" echo "--- ldconfig DOF libs ---" ldconfig -p 2>/dev/null | grep -Ei 'libusb|hidapi|serialport|ftdi|evdev|openal|tinyxml2|boost_system|boost_filesystem|curl|ssl' || true echo echo "--- pkg-config DOF libs ---" for pc in libusb-1.0 hidapi-libusb hidapi-hidraw libserialport libudev tinyxml2 sdl2; do if pkg-config --exists "$pc" 2>/dev/null; then echo "OK pkg-config: $pc $(pkg-config --modversion "$pc" 2>/dev/null || true)" else echo "WARN pkg-config absent: $pc" fi done echo echo "--- groupes pinball ---" id pinball || true echo echo "--- dossier DOF ---" ls -ld /opt/pincabos/apps/dof /opt/pincabos/logs/dof ls -lh /etc/udev/rules.d/99-pincabos-dof.rules pco_check "4B) Installation dépendances DOF / toys / hardware cabinet" # === PinCabOS managed block: dof-dependencies END === pco_step "5) Preparation dossiers VPX/VPinFE selon version.json" id pinball >/dev/null 2>&1 || useradd -m -s /bin/bash pinball mkdir -p \ "$PCO_VPX_DIR" \ "$PCO_VPINFE_DIR" \ "$PCO_TABLES_DIR" \ "$PCO_ROMS_DIR" \ /opt/pincabos/apps/dof \ "$(dirname "$PCO_VPX_INI")" \ /home/pinball/.config \ /home/pinball/.local/share \ /home/pinball/.vpinball \ /opt/pincabos/logs/updates \ /opt/pincabos/logs/admin \ /opt/pincabos/logs/publish \ /opt/pincabos/logs/cleanup chown -R pinball:pinball \ "$PCO_VPX_DIR" \ "$PCO_VPINFE_DIR" \ "$PCO_TABLES_DIR" \ "$PCO_ROMS_DIR" \ "$(dirname "$PCO_VPX_INI")" \ /home/pinball/.config \ /home/pinball/.local \ /home/pinball/.vpinball \ /opt/pincabos/logs chmod -R 775 /opt/pincabos/logs echo "VPX dir : $PCO_VPX_DIR" echo "VPinFE dir : $PCO_VPINFE_DIR" echo "Tables dir : $PCO_TABLES_DIR" echo "ROMs dir : $PCO_ROMS_DIR" echo "VPX ini : $PCO_VPX_INI" # === PinCabOS managed block: cleanup-runtime-compat BEGIN === # Compat runtime pour pincabos-cleanup.sh. # Ne change pas les vrais chemins VPX/VPinFE, ajoute seulement les chemins attendus par validation cleanup. mkdir -p /opt/pincabos/bin /opt/pincabos/apps if [ ! -d /opt/pincabos/web/.venv ] && [ -d /opt/pincabos/web ]; then python3 -m venv /opt/pincabos/web/.venv || true fi if [ ! -e /opt/pincabos/apps/vpx ]; then if [ -d /opt/pincabos/apps/vpinball ]; then ln -sfn /opt/pincabos/apps/vpinball /opt/pincabos/apps/vpx else mkdir -p /opt/pincabos/apps/vpx fi fi if [ ! -d /opt/pincabos/apps/dof ]; then mkdir -p /opt/pincabos/apps/dof cat > /opt/pincabos/apps/dof/README.pincabos <<'EOF_DOF' PinCabOS DOF directory placeholder. DOF can be installed later. This directory exists so safe cleanup/runtime validation does not fail on optional DOF. EOF_DOF fi cat > /opt/pincabos/bin/vpx.sh <<'EOF_VPX_WRAPPER' #!/usr/bin/env bash set -e export HOME="/home/pinball" export XDG_CONFIG_HOME="/home/pinball/.config" export DISPLAY="${DISPLAY:-:0}" export XAUTHORITY="${XAUTHORITY:-/home/pinball/.Xauthority}" if [ -x "/usr/local/bin/pincabos-run-vpx" ]; then exec /usr/local/bin/pincabos-run-vpx "$@" elif [ -x "/usr/local/bin/pincabos-vpx" ]; then exec /usr/local/bin/pincabos-vpx "$@" else VPX_BIN="$(find /opt/pincabos/apps -type f \( -iname 'VPinballX_GL' -o -iname 'VPinballX_BGFX' -o -iname 'VPinballX' -o -iname 'vpinball' -o -iname 'VPinballX*' \) -perm /111 2>/dev/null | head -n1 || true)" if [ -n "$VPX_BIN" ] && [ -x "$VPX_BIN" ]; then exec "$VPX_BIN" "$@" fi fi echo "NOGOOD: aucun executable VPX trouve." exit 1 EOF_VPX_WRAPPER chmod +x /opt/pincabos/bin/vpx.sh chown -R pinball:pinball /opt/pincabos/web/.venv /opt/pincabos/apps/dof /opt/pincabos/bin/vpx.sh 2>/dev/null || true echo "Compat cleanup runtime:" ls -ld /opt/pincabos/web/.venv /opt/pincabos/apps/vpx /opt/pincabos/apps/dof 2>/dev/null || true ls -lh /opt/pincabos/bin/vpx.sh 2>/dev/null || true # === PinCabOS managed block: cleanup-runtime-compat END === pco_check "5) Preparation dossiers VPX/VPinFE selon version.json" pco_step "6) Synchronisation fichiers PinCabOS selon version.json" echo "Aucun fichier sync explicite traite dans cette version clean." pco_check "6) Synchronisation fichiers PinCabOS selon version.json" pco_step "7) Synchronisation arbre PinCabOS depuis update.pincabos.cc" PIN_UPDATE_BASE="${PIN_UPDATE_BASE:-https://ins.pincabos.cc/updates}" PIN_STAGE="/opt/pincabos/stage/pincabos-tree-${TS}" PIN_ROOT="${PIN_STAGE}/root" TREE_DETAIL_LOG="/opt/pincabos/logs/02-install-engine-tree-download-${TS}.detail.log" mkdir -p "$PIN_ROOT" /opt/pincabos/logs /opt/pincabos/state : > "$TREE_DETAIL_LOG" echo "Update base : $PIN_UPDATE_BASE" echo "Stage : $PIN_STAGE" echo "Root stage : $PIN_ROOT" echo "Detail log : $TREE_DETAIL_LOG" echo "Tree sync complet garde dans la version depot. Cette version clean evite de reecraser 02-install-engine.sh pendant son execution." pco_check "7) Synchronisation arbre PinCabOS depuis update.pincabos.cc" pco_step "8) Configuration WebApp PinCabOS et Nginx /first-run" echo "Configuration WebApp + Nginx PinCabOS" WEB_DIR="/opt/pincabos/web" WEB_HOST="${PINCABOS_WEB_HOST:-127.0.0.1}" WEB_PORT="${PINCABOS_WEB_PORT:-5055}" CONSOLE_HOST="${PINCABOS_CONSOLE_HOST:-127.0.0.1}" CONSOLE_PORT="${PINCABOS_CONSOLE_PORT:-8090}" WEB_USER="pinball" WEB_GROUP="pinball" mkdir -p "$WEB_DIR" \ /opt/pincabos/logs \ /opt/pincabos/logs/updates \ /opt/pincabos/logs/admin \ /opt/pincabos/logs/publish \ /opt/pincabos/logs/cleanup \ /opt/pincabos/state chown -R "$WEB_USER:$WEB_GROUP" /opt/pincabos/logs 2>/dev/null || true chmod -R 775 /opt/pincabos/logs 2>/dev/null || true if [ ! -f "$WEB_DIR/app.py" ]; then echo "ERREUR: $WEB_DIR/app.py absent." exit 1 fi # Forcer la WebApp PinCabOS hors des ports VPinFE 8000/8001. # La WebApp reste publique sur :80 via Nginx, mais son backend Python interne doit etre :5055. cp -a "$WEB_DIR/app.py" "$WEB_DIR/app.py.backup-engine-web-port-${TS}" 2>/dev/null || true python3 - "$WEB_DIR/app.py" <<'PYWEBPORT' from pathlib import Path import re import sys p = Path(sys.argv[1]) s = p.read_text(errors="replace") if not re.search(r'^\s*import\s+os\s*$', s, re.M): lines = s.splitlines() insert_at = 0 while insert_at < len(lines) and (lines[insert_at].startswith("#!") or lines[insert_at].strip() == ""): insert_at += 1 lines.insert(insert_at, "import os") s = "\n".join(lines) + "\n" s = re.sub(r'port\s*=\s*8000', 'port=int(os.environ.get("PINCABOS_WEB_PORT", os.environ.get("PCO_WEB_PORT", "5055")))', s) s = re.sub(r'port\s*=\s*8001', 'port=int(os.environ.get("PINCABOS_WEB_PORT", os.environ.get("PCO_WEB_PORT", "5055")))', s) s = re.sub(r'host\s*=\s*["\']0\.0\.0\.0["\']', 'host=os.environ.get("PINCABOS_WEB_HOST", os.environ.get("PCO_WEB_HOST", "127.0.0.1"))', s) s = re.sub(r'host\s*=\s*["\']127\.0\.0\.1["\']', 'host=os.environ.get("PINCABOS_WEB_HOST", os.environ.get("PCO_WEB_HOST", "127.0.0.1"))', s) s = re.sub( r'app\.run\(\s*\)', 'app.run(host=os.environ.get("PINCABOS_WEB_HOST", os.environ.get("PCO_WEB_HOST", "127.0.0.1")), port=int(os.environ.get("PINCABOS_WEB_PORT", os.environ.get("PCO_WEB_PORT", "5055"))))', s ) s = s.replace("127.0.0.1:8000", "127.0.0.1:5055") s = s.replace("localhost:8000", "localhost:5055") s = s.replace("127.0.0.1:8001", "127.0.0.1:5055") s = s.replace("localhost:8001", "localhost:5055") p.write_text(s) PYWEBPORT mkdir -p /etc/pincabos cat > /etc/pincabos/web.env < /etc/pincabos/console.env < /etc/systemd/system/pincabos-web.service.d/10-pincabos-web-port.conf < /etc/systemd/system/pincabos-console.service.d/10-pincabos-console-port.conf < /etc/systemd/system/pincabos-web.service </dev/null || true fi cat > /etc/nginx/sites-available/pincabos-web.conf </dev/null || true # === PinCabOS managed block: webapp-sudoers BEGIN === # Sudoers officiel PinCabOS WebApp. # Requis pour les actions WebApp qui utilisent sudo -n sans mot de passe interactif. # Ne pas utiliser Defaults:pinball !requiretty sur Ubuntu: setting non supporte. SUDOERS_WEBAPP="/etc/sudoers.d/pincabos-web" cat > "$SUDOERS_WEBAPP" <<'EOF_SUDOERS' # PinCabOS WebApp sudo permissions # User: pinball # Purpose: allow approved PinCabOS WebApp actions through sudo -n without interactive password. Cmnd_Alias PINCABOS_TOOLS = \ /opt/pincabos/tools/pincabos-cleanup.sh, \ /opt/pincabos/tools/pincabos-cleanup.sh *, \ /opt/pincabos/tools/publish.sh, \ /opt/pincabos/tools/publish.sh *, \ /opt/pincabos/tools/pincabos-publish.sh, \ /opt/pincabos/tools/pincabos-publish.sh *, \ /opt/pincabos/tools/pincabos-publish-tree.sh, \ /opt/pincabos/tools/pincabos-publish-tree.sh *, \ /opt/pincabos/tools/pincabos-update.sh, \ /opt/pincabos/tools/pincabos-update.sh *, \ /opt/pincabos/tools/firstrun-network-detect.sh, \ /opt/pincabos/tools/firstrun-network-detect.sh *, \ /opt/pincabos/tools/detect-gpu.sh, \ /opt/pincabos/tools/detect-gpu.sh *, \ /opt/pincabos/tools/detect-screens.sh, \ /opt/pincabos/tools/detect-screens.sh *, \ /opt/pincabos/tools/apply-screens.sh, \ /opt/pincabos/tools/apply-screens.sh *, \ /opt/pincabos/tools/audio-ssf-apply.sh, \ /opt/pincabos/tools/audio-ssf-apply.sh *, \ /opt/pincabos/tools/run-console.sh, \ /opt/pincabos/tools/run-console.sh * Cmnd_Alias PINCABOS_SYSTEMCTL = \ /usr/bin/systemctl status pincabos-web.service, \ /usr/bin/systemctl start pincabos-web.service, \ /usr/bin/systemctl stop pincabos-web.service, \ /usr/bin/systemctl restart pincabos-web.service, \ /usr/bin/systemctl status pincabos-console.service, \ /usr/bin/systemctl start pincabos-console.service, \ /usr/bin/systemctl stop pincabos-console.service, \ /usr/bin/systemctl restart pincabos-console.service, \ /usr/bin/systemctl status pincabos-vpinfe.service, \ /usr/bin/systemctl start pincabos-vpinfe.service, \ /usr/bin/systemctl stop pincabos-vpinfe.service, \ /usr/bin/systemctl restart pincabos-vpinfe.service, \ /usr/bin/systemctl status pincabos-frontend.service, \ /usr/bin/systemctl is-active pincabos-frontend.service, \ /usr/bin/systemctl is-enabled pincabos-frontend.service, \ /usr/bin/systemctl start pincabos-frontend.service, \ /usr/bin/systemctl stop pincabos-frontend.service, \ /usr/bin/systemctl restart pincabos-frontend.service, \ /usr/bin/systemctl enable pincabos-frontend.service, \ /usr/bin/systemctl disable pincabos-frontend.service, \ /usr/bin/systemctl enable --now pincabos-frontend.service, \ /usr/bin/systemctl disable --now pincabos-frontend.service, \ /usr/bin/systemctl status nginx, \ /usr/bin/systemctl start nginx, \ /usr/bin/systemctl stop nginx, \ /usr/bin/systemctl restart nginx Cmnd_Alias PINCABOS_POWER = \ /usr/sbin/reboot, \ /sbin/reboot pinball ALL=(root) NOPASSWD: PINCABOS_TOOLS, PINCABOS_SYSTEMCTL, PINCABOS_POWER EOF_SUDOERS chmod 440 "$SUDOERS_WEBAPP" visudo -cf "$SUDOERS_WEBAPP" echo echo "=== Validation sudo -n pinball ===" sudo -u pinball sudo -n -l | sed -n '1,180p' || true echo echo "=== Test sudo -n cleanup autorisation seulement ===" if sudo -u pinball sudo -n -l /opt/pincabos/tools/pincabos-cleanup.sh >/dev/null 2>&1; then echo "OK: pinball peut lancer cleanup via sudo -n" else echo "WARN: pinball ne peut pas lancer cleanup via sudo -n" fi # === PinCabOS managed block: webapp-sudoers END === systemctl daemon-reload systemctl enable pincabos-web.service || true systemctl restart pincabos-web.service || true if systemctl list-unit-files | grep -q '^pincabos-console.service'; then systemctl enable pincabos-console.service || true systemctl restart pincabos-console.service || true fi nginx -t systemctl restart nginx echo echo "=== Validation HTTP locale ===" ss -ltnp | grep -E ':80|:5055|:8090|:8000|:8001' || true echo echo "--- Backend WebApp direct ${WEB_PORT}" curl -i --max-time 5 "http://${WEB_HOST}:${WEB_PORT}/" | sed -n '1,40p' || true echo echo "--- Console direct ${CONSOLE_PORT}" curl -i --max-time 5 "http://${CONSOLE_HOST}:${CONSOLE_PORT}/" | sed -n '1,40p' || true echo echo "--- PinCabOS via port 80" curl -i --max-time 5 http://127.0.0.1/first-run | sed -n '1,80p' || true pco_write_nginx_canonical_webapp pco_check "8) Configuration WebApp PinCabOS et Nginx /first-run" # === PinCabOS managed block: webapp-local-icons-fontawesome BEGIN === pco_step "ICON) Installation Font Awesome WebApp + Openbox" FA_WEB_DIR="/opt/pincabos/web/static/vendor/fontawesome" FA_SYS_DIR="/usr/local/share/fonts/fontawesome" FA_USER_DIR="/home/pinball/.local/share/fonts/fontawesome" mkdir -p "$FA_WEB_DIR/css" "$FA_WEB_DIR/webfonts" "$FA_SYS_DIR" "$FA_USER_DIR" echo "Objectif:" echo "- WebApp CSS local : $FA_WEB_DIR" echo "- Fontconfig system: $FA_SYS_DIR" echo "- Fontconfig user : $FA_USER_DIR" echo echo "=== Installation paquets Font Awesome/fontconfig ===" run_spin_bash "Installation Font Awesome + fontconfig" 'DEBIAN_FRONTEND=noninteractive apt-get install -y fontconfig fonts-font-awesome || true' echo echo "=== Recherche fichiers Font Awesome disponibles ===" FA_FOUND_LIST="/tmp/pincabos-fontawesome-found-${TS}.txt" : > "$FA_FOUND_LIST" find /usr/share /opt/pincabos /home/pinball -type f \( \ -iname "*font*awesome*.ttf" -o \ -iname "*font*awesome*.otf" -o \ -iname "*fontawesome*.ttf" -o \ -iname "*fontawesome*.otf" -o \ -iname "fa-*.ttf" -o \ -iname "fa-*.otf" -o \ -iname "fa-*.woff" -o \ -iname "fa-*.woff2" -o \ -iname "*fontawesome*.woff" -o \ -iname "*fontawesome*.woff2" \ \) 2>/dev/null | sort -u > "$FA_FOUND_LIST" cat "$FA_FOUND_LIST" || true echo echo "=== Copier fonts système pour Openbox/fontconfig ===" while IFS= read -r f; do [ -f "$f" ] || continue case "$f" in *.ttf|*.TTF|*.otf|*.OTF) echo "Fontconfig: $f" cp -f "$f" "$FA_SYS_DIR/" 2>/dev/null || true cp -f "$f" "$FA_USER_DIR/" 2>/dev/null || true ;; esac done < "$FA_FOUND_LIST" echo echo "=== Copier webfonts pour WebApp ===" while IFS= read -r f; do [ -f "$f" ] || continue case "$f" in *.woff|*.WOFF|*.woff2|*.WOFF2|*.ttf|*.TTF|*.otf|*.OTF) cp -f "$f" "$FA_WEB_DIR/webfonts/" 2>/dev/null || true ;; esac done < "$FA_FOUND_LIST" echo echo "=== Copier CSS Font Awesome Debian si présent ===" for cssdir in \ /usr/share/javascript/font-awesome/css \ /usr/share/font-awesome/css \ /usr/share/fonts-font-awesome/css do if [ -d "$cssdir" ]; then cp -af "$cssdir"/. "$FA_WEB_DIR/css/" 2>/dev/null || true fi done echo echo "=== Créer CSS fallback compatible FA4/FA5/FA6 ===" cat > "$FA_WEB_DIR/css/all.min.css" <<'EOF_FA_CSS' @font-face { font-family: "FontAwesome"; font-style: normal; font-weight: normal; font-display: block; src: url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.woff2") format("woff2"), url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.woff") format("woff"), url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.ttf") format("truetype"), url("/static/vendor/fontawesome/webfonts/fa-solid-900.woff2") format("woff2"), url("/static/vendor/fontawesome/webfonts/fa-solid-900.ttf") format("truetype"); } @font-face { font-family: "Font Awesome 6 Free"; font-style: normal; font-weight: 900; font-display: block; src: url("/static/vendor/fontawesome/webfonts/fa-solid-900.woff2") format("woff2"), url("/static/vendor/fontawesome/webfonts/fa-solid-900.ttf") format("truetype"), url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.woff2") format("woff2"), url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.woff") format("woff"), url("/static/vendor/fontawesome/webfonts/fontawesome-webfont.ttf") format("truetype"); } .fa, .fas, .fa-solid, .far, .fab { font-family: "Font Awesome 6 Free", "FontAwesome" !important; font-weight: 900 !important; font-style: normal !important; text-rendering: auto; -webkit-font-smoothing: antialiased; } /* Icônes utilisées dans PinCabOS */ .fa-toolbox:before { content: "\f552"; } .fa-tools:before { content: "\f7d9"; } .fa-wrench:before { content: "\f0ad"; } .fa-gear:before, .fa-cog:before { content: "\f013"; } .fa-play:before { content: "\f04b"; } .fa-stop:before { content: "\f04d"; } .fa-power-off:before { content: "\f011"; } .fa-download:before { content: "\f019"; } .fa-upload:before { content: "\f093"; } .fa-folder:before { content: "\f07b"; } .fa-terminal:before { content: "\f120"; } .fa-broom:before { content: "\f51a"; } .fa-sync:before, .fa-rotate:before { content: "\f2f1"; } .fa-shield-alt:before, .fa-shield-halved:before { content: "\f3ed"; } .fa-list:before { content: "\f03a"; } .fa-file-lines:before { content: "\f15c"; } .fa-gamepad:before { content: "\f11b"; } .fa-compact-disc:before { content: "\f51f"; } EOF_FA_CSS echo echo "=== Injecter CSS Font Awesome dans WebApp ===" if [ -d /opt/pincabos/web/templates ]; then for html in /opt/pincabos/web/templates/*.html; do [ -f "$html" ] || continue if ! grep -q "/static/vendor/fontawesome/css/all.min.css" "$html"; then sed -i '0,/]*>/s//&\ /' "$html" || true fi done fi if [ -f /opt/pincabos/web/app.py ]; then if ! grep -q "/static/vendor/fontawesome/css/all.min.css" /opt/pincabos/web/app.py; then python3 - <<'PYAPP' from pathlib import Path p = Path("/opt/pincabos/web/app.py") s = p.read_text(errors="replace") link = '' if link not in s: if "" in s: s = s.replace("", "\n " + link, 1) elif "\n" in s: s = s.replace("\n", "\n " + link + "\n", 1) p.write_text(s) PYAPP fi fi echo echo "=== Permissions + font cache ===" chown -R pinball:pinball "$FA_WEB_DIR" "$FA_USER_DIR" 2>/dev/null || true chown -R root:root "$FA_SYS_DIR" 2>/dev/null || true chmod -R a+rX "$FA_WEB_DIR" "$FA_SYS_DIR" "$FA_USER_DIR" 2>/dev/null || true fc-cache -f -v "$FA_SYS_DIR" || true sudo -u pinball fc-cache -f -v "$FA_USER_DIR" || true fc-cache -f -v || true echo echo "=== Vérification Font Awesome détecté par fontconfig ===" fc-list | grep -i "awesome" || true echo echo "=== fc-match familles courantes ===" fc-match "FontAwesome" || true fc-match "Font Awesome 6 Free" || true fc-match "Font Awesome 5 Free" || true echo echo "=== Vérification WebApp assets ===" find "$FA_WEB_DIR" -maxdepth 3 -type f | sort | sed -n '1,120p' systemctl restart pincabos-web.service 2>/dev/null || true systemctl restart nginx 2>/dev/null || true echo "Font Awesome installé pour WebApp + Openbox/fontconfig." pco_check "ICON) Installation Font Awesome WebApp + Openbox" # === PinCabOS managed block: webapp-local-icons-fontawesome END === pco_guard_vpinfe_ports "apres configuration WebApp/Nginx avant VPinFE" pco_step "9) Resolution depot officiel VPX Standalone Linux 64" PCO_VPX_URL="$(github_latest_asset_url "vpinball/vpinball" '((VPinballX|VPinballX_GL|VPinballX_BGFX).*(linux|Linux).*(x64|amd64|x86_64).*\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.zst)$)|((linux|Linux).*(x64|amd64|x86_64).*(VPinballX|VPinballX_GL|VPinballX_BGFX).*\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.zst)$)')" if [ -z "${PCO_VPX_URL:-}" ]; then echo "ERREUR: impossible de trouver l'asset VPX Standalone Linux 64 dans le depot officiel vpinball/vpinball." exit 1 fi echo "VPX officiel:" echo "$PCO_VPX_URL" pco_check "9) Resolution depot officiel VPX Standalone Linux 64" pco_step "10) Telechargement VPX Standalone Linux 64 dans /opt/pincabos/download" VPX_DOWNLOAD_DIR="${DOWNLOAD_DIR}/vpx" mkdir -p "$VPX_DOWNLOAD_DIR" VPX_ARCHIVE="${VPX_DOWNLOAD_DIR}/$(basename "${PCO_VPX_URL%%\?*}")" VPX_TMP="${VPX_ARCHIVE}.download-${TS}" run_spin "Telechargement VPX officiel" curl -fL "$PCO_VPX_URL" -o "$VPX_TMP" if [ ! -s "$VPX_TMP" ]; then echo "ERREUR: archive VPX telechargee vide: $VPX_TMP" exit 1 fi mv -f "$VPX_TMP" "$VPX_ARCHIVE" chmod 0644 "$VPX_ARCHIVE" ls -lh "$VPX_ARCHIVE" file "$VPX_ARCHIVE" || true pco_check "10) Telechargement VPX Standalone Linux 64 dans /opt/pincabos/download" pco_step "11) Preparation et installation VPX Standalone" VPX_WORKDIR="${VPX_DOWNLOAD_DIR}/prepared-${TS}" mkdir -p "$VPX_WORKDIR" VPX_PAYLOAD_DIR="$(prepare_archive_payload "$VPX_ARCHIVE" "$VPX_WORKDIR" "VPX")" rsync -a --delete "$VPX_PAYLOAD_DIR"/ "$PCO_VPX_DIR"/ find "$PCO_VPX_DIR" -type f \( -iname "VPinballX*" -o -iname "vpinball*" \) -exec chmod +x {} \; 2>/dev/null || true chown -R pinball:pinball "$PCO_VPX_DIR" PCO_VPX_BIN="$(find_executable "$PCO_VPX_DIR" "VPinballX_GL" "VPinballX_BGFX" "VPinballX" "vpinball" "VPinballX*")" if [ -z "${PCO_VPX_BIN:-}" ]; then echo "ERREUR: aucun executable VPX trouve dans $PCO_VPX_DIR" exit 1 fi ln -sf "$PCO_VPX_BIN" /usr/local/bin/pincabos-vpx echo "VPX binaire: $PCO_VPX_BIN" pco_check "11) Preparation et installation VPX Standalone" pco_step "12) Configuration VPX Standalone" if [ ! -f "$PCO_VPX_INI" ]; then cat > "$PCO_VPX_INI" </dev/null || true)" if [ -z "${VPINFE_REAL_BIN:-}" ]; then echo "ERREUR: aucun fichier ./vpinfe trouve dans l'archive." exit 1 fi VPINFE_PAYLOAD="$(dirname "$VPINFE_REAL_BIN")" fi mkdir -p "$(dirname "$PCO_VPINFE_DIR")" rm -rf "$PCO_VPINFE_DIR" mkdir -p "$PCO_VPINFE_DIR" rsync -a --delete "$VPINFE_PAYLOAD"/ "$PCO_VPINFE_DIR"/ PCO_VPINFE_BIN="${PCO_VPINFE_DIR}/vpinfe" if [ ! -f "$PCO_VPINFE_BIN" ]; then echo "ERREUR: binaire attendu absent apres copie: $PCO_VPINFE_BIN" exit 1 fi chmod -R a+rX "$PCO_VPINFE_DIR" || true chmod +x "$PCO_VPINFE_BIN" || true chown -R pinball:pinball "$(dirname "$PCO_VPINFE_DIR")" ln -sfn "$PCO_VPINFE_BIN" /usr/local/bin/pincabos-vpinfe mkdir -p /opt/pincabos/state printf "%s\n" "$PCO_VPINFE_BIN" > /opt/pincabos/state/vpinfe-bin.path echo "VPinFE binaire: $PCO_VPINFE_BIN" ls -lh "$PCO_VPINFE_BIN" file "$PCO_VPINFE_BIN" || true pco_check "15) Preparation et installation VPinFE" pco_step "16) Configuration VPinFE" # === PinCabOS managed block: vpinfe-default-dirs BEGIN === # VPinFE officiel: # - Template source PinCabOS: # /opt/pincabos/essentials/VPinFEfiles/vpinfe.ini # - Runtime réellement utilisé par VPinFE: # /home/pinball/.config/vpinfe/vpinfe.ini # # Règle: # On part du fichier officiel VPinFE sauvegardé dans essentials/VPinFEfiles. # On ne reconstruit pas tout le INI à la main. # On ajuste seulement les valeurs PinCabOS nécessaires. VPINFE_TEMPLATE_INI="/opt/pincabos/essentials/VPinFEfiles/vpinfe.ini" VPINFE_RUNTIME_CONFIG_DIR="/home/pinball/.config/vpinfe" VPINFE_RUNTIME_INI="${VPINFE_RUNTIME_CONFIG_DIR}/vpinfe.ini" VPINFE_PINCABOS_CONFIG_DIR="/opt/pincabos/config/vpinfe" VPINFE_PINCABOS_INI="${VPINFE_PINCABOS_CONFIG_DIR}/vpinfe.ini" VPINFE_DEFAULT_VPXBIN="${PCO_VPX_BIN:-/opt/pincabos/apps/vpinball/VPinballX_GL}" VPINFE_DEFAULT_TABLES="/home/pinball/Tables" VPINFE_DEFAULT_VPXINI="${PCO_VPX_INI:-/home/pinball/.vpinball/VPinballX.ini}" VPINFE_DEFAULT_VPXDIR="${PCO_VPX_DIR:-/opt/pincabos/apps/vpinball}" VPINFE_DEFAULT_VPINFE_DIR="${PCO_VPINFE_DIR:-/opt/pincabos/apps/frontend/vpinfe/current}" VPINFE_DEFAULT_ROMS="${PCO_ROMS_DIR:-/opt/pincabos/apps/vpinball/PinMAME/roms}" mkdir -p \ "$VPINFE_RUNTIME_CONFIG_DIR" \ "$VPINFE_PINCABOS_CONFIG_DIR" \ "/opt/pincabos/essentials/VPinFEfiles" \ "$VPINFE_DEFAULT_TABLES" \ "$(dirname "$VPINFE_DEFAULT_VPXINI")" \ "$VPINFE_DEFAULT_ROMS" # Si le template n'existe pas encore, préserver un runtime existant comme template. # Sinon créer un INI minimal seulement comme fallback de sécurité. if [ ! -f "$VPINFE_TEMPLATE_INI" ]; then if [ -f "$VPINFE_RUNTIME_INI" ]; then cp -a "$VPINFE_RUNTIME_INI" "$VPINFE_TEMPLATE_INI" elif [ -f "$VPINFE_PINCABOS_INI" ]; then cp -a "$VPINFE_PINCABOS_INI" "$VPINFE_TEMPLATE_INI" else cat > "$VPINFE_TEMPLATE_INI" <<'EOF_VPINFE_TEMPLATE' [Displays] bgscreenid = dmdscreenid = bgwindowoverride = dmdwindowoverride = tablescreenid = 0 tableorientation = landscape tablerotation = 0 cabmode = false [Settings] vpxbinpath = vpxlaunchenv = globalinioverride = globaltableinioverrideenabled = false globaltableinioverridemask = tablerootdir = vpxinipath = vpxlogdeleteonstart = false theme = Revolution startup_collection = autoupdatemediaonstartup = false splashscreen = false muteaudio = false chromeoptions = disabledefaultchromeoptions = false mmhidequitbutton = false [Logger] level = debug console = true [Network] themeassetsport = 8000 manageruiport = 8001 [DOF] enabledof = false dofconfigtoolapikey = EOF_VPINFE_TEMPLATE fi fi # Backup runtime actuel si présent. if [ -f "$VPINFE_RUNTIME_INI" ]; then cp -a "$VPINFE_RUNTIME_INI" "${VPINFE_RUNTIME_INI}.backup-engine-${TS}" 2>/dev/null || true fi # Copier le template officiel comme base runtime. cp -a "$VPINFE_TEMPLATE_INI" "$VPINFE_RUNTIME_INI" # Ajuster seulement les valeurs PinCabOS nécessaires. python3 - "$VPINFE_RUNTIME_INI" "$VPINFE_DEFAULT_VPXBIN" "$VPINFE_DEFAULT_TABLES" "$VPINFE_DEFAULT_VPXINI" "$VPINFE_DEFAULT_VPXDIR" "$VPINFE_DEFAULT_VPINFE_DIR" "$VPINFE_DEFAULT_ROMS" <<'PY_VPINFE_INI' import configparser import sys from pathlib import Path ini, vpxbin, tables, vpxini, vpxdir, vpinfedir, romsdir = sys.argv[1:] p = Path(ini) cfg = configparser.ConfigParser() cfg.optionxform = str cfg.read(p, encoding="utf-8") def ensure(section): if not cfg.has_section(section): cfg.add_section(section) def setv(section, key, value): ensure(section) cfg.set(section, key, value) # Cabinet defaults PinCabOS. # Ces valeurs ne changent pas les ports VPinFE, seulement les chemins et le mode cab. setv("Displays", "cabmode", "true") setv("Displays", "tablescreenid", "0") # Ne pas forcer bgscreenid/dmdscreenid si le user les a déjà vides/officiels. # Mais si absents, ajouter les defaults PinCabOS. ensure("Displays") if not cfg.has_option("Displays", "bgscreenid"): cfg.set("Displays", "bgscreenid", "1") if not cfg.has_option("Displays", "dmdscreenid"): cfg.set("Displays", "dmdscreenid", "2") # Chemins officiels PinCabOS. setv("Settings", "tablerootdir", tables) setv("Settings", "vpxbinpath", vpxbin) setv("Settings", "vpxinipath", vpxini) # Garder ports officiels VPinFE. setv("Network", "themeassetsport", "8000") setv("Network", "manageruiport", "8001") # Section référence PinCabOS. setv("PinCabOS", "vpxdir", vpxdir) setv("PinCabOS", "vpinfedir", vpinfedir) setv("PinCabOS", "romsdir", romsdir) setv("PinCabOS", "tablesdir", tables) setv("PinCabOS", "vpxbinpath", vpxbin) setv("PinCabOS", "vpxinipath", vpxini) with open(p, "w", encoding="utf-8") as f: cfg.write(f) PY_VPINFE_INI # Garder une copie référence PinCabOS du runtime final. cp -f "$VPINFE_RUNTIME_INI" "$VPINFE_PINCABOS_INI" # Compat Tables: app VPX peut voir Tables, mais les tables officielles restent /home/pinball/Tables. if [ -d "/opt/pincabos/apps/vpinball/Tables" ] && [ ! -L "/opt/pincabos/apps/vpinball/Tables" ]; then if [ -z "$(find /opt/pincabos/apps/vpinball/Tables -mindepth 1 -print -quit 2>/dev/null)" ]; then rmdir /opt/pincabos/apps/vpinball/Tables 2>/dev/null || true fi fi if [ ! -e "/opt/pincabos/apps/vpinball/Tables" ]; then ln -sfn "$VPINFE_DEFAULT_TABLES" "/opt/pincabos/apps/vpinball/Tables" 2>/dev/null || true fi # Créer vpx.ini minimal si absent, sans écraser. if [ ! -f "$VPINFE_DEFAULT_VPXINI" ]; then cat > "$VPINFE_DEFAULT_VPXINI" <<'EOF_VPXINI' [Player] FullScreen = 1 [Standalone] PinCabOS = 1 EOF_VPXINI fi chown -R pinball:pinball \ "$VPINFE_RUNTIME_CONFIG_DIR" \ "$VPINFE_PINCABOS_CONFIG_DIR" \ "/opt/pincabos/essentials/VPinFEfiles" \ "$VPINFE_DEFAULT_TABLES" \ "$(dirname "$VPINFE_DEFAULT_VPXINI")" \ "$VPINFE_DEFAULT_ROMS" 2>/dev/null || true chmod 755 "$VPINFE_RUNTIME_CONFIG_DIR" "$VPINFE_PINCABOS_CONFIG_DIR" "/opt/pincabos/essentials/VPinFEfiles" 2>/dev/null || true chmod 644 "$VPINFE_RUNTIME_INI" "$VPINFE_PINCABOS_INI" "$VPINFE_TEMPLATE_INI" "$VPINFE_DEFAULT_VPXINI" 2>/dev/null || true echo echo "=== VPinFE template officiel PinCabOS ===" ls -lh "$VPINFE_TEMPLATE_INI" echo echo "=== VPinFE runtime actif final ===" ls -lh "$VPINFE_RUNTIME_INI" grep -nEi 'cabmode|tablescreenid|bgscreenid|dmdscreenid|tablerootdir|vpxbinpath|vpxinipath|themeassetsport|manageruiport|vpxdir|vpinfedir|romsdir' "$VPINFE_RUNTIME_INI" || true echo echo "=== VPinFE référence PinCabOS finale ===" ls -lh "$VPINFE_PINCABOS_INI" echo echo "=== Validation chemins VPinFE ===" for path in \ "$VPINFE_DEFAULT_VPXBIN" \ "$VPINFE_DEFAULT_TABLES" \ "$VPINFE_DEFAULT_VPXINI" \ "$VPINFE_DEFAULT_VPXDIR" \ "$VPINFE_DEFAULT_VPINFE_DIR" \ "$VPINFE_DEFAULT_ROMS" do if [ -e "$path" ]; then echo "OK: $path" else echo "WARN absent: $path" fi done # === PinCabOS managed block: vpinfe-default-dirs END === pco_check "16) Configuration VPinFE" pco_step "17) Creation raccourcis engine PinCabOS" cat > /usr/local/bin/pincabos-run-vpx < /usr/local/bin/pincabos-run-vpinfe < /usr/share/applications/pincabos-vpinfe.desktop </dev/null || true pco_check "17) Creation raccourcis engine PinCabOS" pco_step "18) Creation service VPinFE optionnel" cat > /etc/systemd/system/pincabos-vpinfe.service < /etc/pincabos/console.env <<'EOF_CONSOLE_ENV' PINCABOS_CONSOLE_HOST=127.0.0.1 PINCABOS_CONSOLE_PORT=8090 CONSOLE_HOST=127.0.0.1 CONSOLE_PORT=8090 HOST=127.0.0.1 PORT=8090 EOF_CONSOLE_ENV chmod 644 /etc/pincabos/console.env cat > /opt/pincabos/console/app.py <<'PY_CONSOLE_APP' #!/usr/bin/env python3 import os import subprocess from html import escape from flask import Flask, Response app = Flask(__name__) def cmd_out(cmd): try: r = subprocess.run(cmd, text=True, capture_output=True, timeout=6) out = (r.stdout or "") + (r.stderr or "") return escape(out[-6000:] if out else "") except Exception as e: return escape(str(e)) @app.route("/") def index(): services = cmd_out([ "/usr/bin/systemctl", "--no-pager", "--plain", "status", "pincabos-web.service", "pincabos-vpinfe.service", "pincabos-console.service", "nginx" ]) ports = cmd_out(["/usr/bin/ss", "-ltnp"]) html = f""" PinCabOS Console

PinCabOS Console Commander

Backend console actif sur 127.0.0.1:8090.

Retour WebApp Tools Commander Admin Console Root

Services

{services}

Ports

{ports}
""" return Response(html, mimetype="text/html") if __name__ == "__main__": host = os.environ.get("PINCABOS_CONSOLE_HOST", os.environ.get("HOST", "127.0.0.1")) port = int(os.environ.get("PINCABOS_CONSOLE_PORT", os.environ.get("PORT", "8090"))) app.run(host=host, port=port, debug=False) PY_CONSOLE_APP chmod +x /opt/pincabos/console/app.py chown -R pinball:pinball /opt/pincabos/console 2>/dev/null || true cat > /etc/systemd/system/pincabos-console.service <<'EOF_CONSOLE_SERVICE' [Unit] Description=PinCabOS Console Commander - Web Terminal After=network-online.target Wants=network-online.target [Service] Type=simple User=pinball Group=pinball WorkingDirectory=/opt/pincabos/console EnvironmentFile=-/etc/pincabos/console.env Environment=PYTHONUNBUFFERED=1 ExecStart=/usr/bin/python3 /opt/pincabos/console/app.py Restart=always RestartSec=3 [Install] WantedBy=multi-user.target EOF_CONSOLE_SERVICE cat > /etc/systemd/system/pincabos-console.service.d/10-pincabos-console-port.conf <<'EOF_CONSOLE_DROPIN' [Service] EnvironmentFile=-/etc/pincabos/console.env EOF_CONSOLE_DROPIN # Wrapper officiel VPinFE. VPINFE_BIN="/opt/pincabos/apps/frontend/vpinfe/current/vpinfe" if [ -f /opt/pincabos/state/vpinfe-bin.path ]; then VPINFE_BIN="$(cat /opt/pincabos/state/vpinfe-bin.path)" fi VPINFE_DIR="$(dirname "$VPINFE_BIN")" cat > /usr/local/bin/pincabos-run-vpinfe < /etc/systemd/system/pincabos-vpinfe.service < /etc/systemd/system/pincabos-frontend.service <<'EOF_FRONTEND_SERVICE' [Unit] Description=PinCabOS Frontend Compatibility Wrapper Documentation=PinCabOS legacy compatibility service. Real frontend is pincabos-vpinfe.service. After=graphical.target network-online.target pincabos-vpinfe.service Wants=network-online.target pincabos-vpinfe.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/systemctl start pincabos-vpinfe.service ExecStop=/usr/bin/systemctl stop pincabos-vpinfe.service [Install] WantedBy=graphical.target EOF_FRONTEND_SERVICE # Nginx: WebApp sur :80 -> 5055, Console /console/ -> 8090. mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled cat > /etc/nginx/sites-available/pincabos-web.conf <<'EOF_NGINX_PINCABOS_WEB' server { listen 80 default_server; listen [::]:80 default_server; server_name _; client_max_body_size 2048m; access_log /var/log/nginx/pincabos-access.log; error_log /var/log/nginx/pincabos-error.log; location /console/ { proxy_pass http://127.0.0.1:8090/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600; proxy_send_timeout 3600; } location /console { return 301 /console/; } location / { proxy_pass http://127.0.0.1:5055; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600; proxy_send_timeout 3600; } } EOF_NGINX_PINCABOS_WEB rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/pincabos-web rm -f /etc/nginx/sites-enabled/pincabos-web.conf ln -sfn /etc/nginx/sites-available/pincabos-web.conf /etc/nginx/sites-enabled/pincabos-web.conf systemctl daemon-reload systemctl enable pincabos-console.service || true systemctl enable pincabos-vpinfe.service || true systemctl enable pincabos-frontend.service || true systemctl restart pincabos-console.service || true if command -v nginx >/dev/null 2>&1; then nginx -t systemctl restart nginx || true fi # Ne pas forcer un deuxième VPinFE si un process vpinfe écoute déjà sur 8000/8001. if ss -ltnp 2>/dev/null | grep -E ':8000|:8001' | grep -q 'vpinfe'; then echo "VPinFE deja actif sur 8000/8001: pas de double demarrage." else systemctl restart pincabos-vpinfe.service || true fi # Le service legacy peut être activé après, il délègue seulement. systemctl start pincabos-frontend.service || true sleep 3 echo "=== Validation Console/Frontend/VPinFE ===" systemctl is-active pincabos-console.service || true systemctl is-active pincabos-vpinfe.service || true systemctl is-active pincabos-frontend.service || true ss -ltnp | grep -E ':80|:5055|:8090|:8000|:8001|:8002' || true curl -I http://127.0.0.1:8090/ --max-time 5 || true curl -I http://127.0.0.1/console/ --max-time 5 || true # === PinCabOS managed block: console-frontend-services END === pco_step "19) Validation engine VPX/VPinFE" echo "=== VPX ===" echo "Dir : $PCO_VPX_DIR" echo "Bin : $PCO_VPX_BIN" ls -lh "$PCO_VPX_BIN" file "$PCO_VPX_BIN" || true ldd "$PCO_VPX_BIN" 2>/dev/null | grep "not found" && exit 1 || true echo echo "=== VPinFE ===" echo "Dir : $PCO_VPINFE_DIR" echo "Bin : $PCO_VPINFE_BIN" ls -lh "$PCO_VPINFE_BIN" file "$PCO_VPINFE_BIN" || true ldd "$PCO_VPINFE_BIN" 2>/dev/null | grep "not found" && exit 1 || true echo echo "=== Config VPinFE essentielle ===" grep -nE 'vpxbinpath|tablerootdir|vpxinipath' "$VPINFE_INI" || true echo echo "=== WebApp / Nginx ===" ls -lh /opt/pincabos/web/app.py systemctl is-active pincabos-web.service || true systemctl is-active nginx || true curl -fsS http://127.0.0.1/first-run >/dev/null || true echo echo "=== Dossiers ===" for p in "$PCO_VPX_DIR" "$PCO_VPINFE_DIR" "$PCO_TABLES_DIR" "$PCO_ROMS_DIR" "$(dirname "$PCO_VPX_INI")"; do ls -ld "$p" done pco_check "19) Validation engine VPX/VPinFE" pco_step "20) Resume final" echo "VPX Standalone Linux 64 : installe depuis depot officiel vpinball/vpinball" echo "VPX URL : $PCO_VPX_URL" echo "VPX dir : $PCO_VPX_DIR" echo "VPX bin : $PCO_VPX_BIN" echo "VPX ini : $PCO_VPX_INI" echo "Tables dir : $PCO_TABLES_DIR" echo "ROMs dir : $PCO_ROMS_DIR" echo "DOF dir : /opt/pincabos/apps/dof" echo "VPinFE : installe depuis depot officiel superhac/vpinfe" echo "VPinFE URL : $PCO_VPINFE_URL" echo "VPinFE dir : $PCO_VPINFE_DIR" echo "VPinFE bin : $PCO_VPINFE_BIN" echo "VPinFE ini : $VPINFE_INI" echo "Service VPinFE : $(systemctl is-enabled pincabos-vpinfe.service 2>/dev/null || true) / $(systemctl is-active pincabos-vpinfe.service 2>/dev/null || true)" echo "Service Frontend compat : $(systemctl is-enabled pincabos-frontend.service 2>/dev/null || true) / $(systemctl is-active pincabos-frontend.service 2>/dev/null || true)" echo "Service Console : $(systemctl is-enabled pincabos-console.service 2>/dev/null || true) / $(systemctl is-active pincabos-console.service 2>/dev/null || true) / port 8090" echo "Service WebApp : $(systemctl is-enabled pincabos-web.service 2>/dev/null || true) / $(systemctl is-active pincabos-web.service 2>/dev/null || true)" echo "Console PinCabOS : $(systemctl is-enabled pincabos-console.service 2>/dev/null || true) / $(systemctl is-active pincabos-console.service 2>/dev/null || true) / port 8090" echo "Nginx : $(systemctl is-active nginx 2>/dev/null || true)" echo "Sudoers WebApp : $(test -f /etc/sudoers.d/pincabos-web && echo present || echo absent)" echo "Version JSON importe : $VERSION_JSON_LOCAL" echo "Manifest sync PinCabOS : $SYNC_MANIFEST" echo "Download dir : $DOWNLOAD_DIR" echo "Espace utilise systeme : $(df -h / | awk 'NR==2 {print $3}')" echo "Espace disponible : $(df -h / | awk 'NR==2 {print $4}')" echo "Utilisation systeme : $(df -h / | awk 'NR==2 {print $5}')" echo "Log : $LOG" pco_check "20) Resume final" # === PINCABOS FIX 02 LIGHTDM AUTOLOGIN SAFETY START === pco_ensure_lightdm_autologin_pinball_02() { echo pco_line 2>/dev/null || echo "----------------------------------------------------------------" printf "${ORANGE:-}PinCabOS - Safety LightDM autologin pinball depuis 02-install-engine${RESET:-} " pco_line 2>/dev/null || echo "----------------------------------------------------------------" if ! command -v lightdm >/dev/null 2>&1 && [ ! -d /etc/lightdm ]; then echo "WARN: LightDM absent, skip autologin safety." return 0 fi id pinball >/dev/null 2>&1 || useradd -m -s /bin/bash pinball # Certains LightDM/PAM exigent un groupe selon distro/config. for grp in autologin nopasswdlogin video input audio render netdev; do if getent group "$grp" >/dev/null 2>&1; then usermod -aG "$grp" pinball 2>/dev/null || true fi done mkdir -p /etc/lightdm/lightdm.conf.d mkdir -p /usr/share/xsessions mkdir -p /usr/local/bin mkdir -p /home/pinball/.config/openbox mkdir -p /home/pinball/.local/share mkdir -p /opt/pincabos/logs cat >/usr/share/xsessions/pincabos-openbox.desktop <<'EOF_XSESSION' [Desktop Entry] Name=PinCabOS Openbox Comment=PinCabOS cabinet session Exec=/usr/local/bin/pincabos-openbox-session TryExec=/usr/local/bin/pincabos-openbox-session Type=Application DesktopNames=PinCabOS;Openbox EOF_XSESSION cat >/usr/local/bin/pincabos-openbox-session <<'EOF_SESSION' #!/usr/bin/env bash set -e export HOME="/home/pinball" export USER="pinball" export LOGNAME="pinball" export XDG_CONFIG_HOME="/home/pinball/.config" export XDG_DATA_HOME="/home/pinball/.local/share" export DISPLAY="${DISPLAY:-:0}" mkdir -p /home/pinball/.config/openbox /home/pinball/.local/share /opt/pincabos/logs chown -R pinball:pinball /home/pinball/.config /home/pinball/.local 2>/dev/null || true if command -v openbox-session >/dev/null 2>&1; then exec openbox-session fi if command -v openbox >/dev/null 2>&1; then exec openbox fi echo "NOGOOD: openbox/openbox-session introuvable" | tee -a /opt/pincabos/logs/lightdm-openbox-session.log sleep infinity EOF_SESSION chmod 755 /usr/local/bin/pincabos-openbox-session chmod 644 /usr/share/xsessions/pincabos-openbox.desktop chown root:root /usr/local/bin/pincabos-openbox-session /usr/share/xsessions/pincabos-openbox.desktop 2>/dev/null || true chown -R pinball:pinball /home/pinball/.config /home/pinball/.local 2>/dev/null || true cat >/etc/lightdm/lightdm.conf <<'EOF_LIGHTDM_MAIN' [LightDM] run-directory=/run/lightdm [Seat:*] greeter-session=lightdm-gtk-greeter user-session=pincabos-openbox autologin-user=pinball autologin-user-timeout=0 autologin-session=pincabos-openbox greeter-hide-users=true greeter-show-manual-login=false allow-guest=false EOF_LIGHTDM_MAIN cat >/etc/lightdm/lightdm.conf.d/50-pincabos-autologin.conf <<'EOF_LIGHTDM_AUTO' [Seat:*] greeter-session=lightdm-gtk-greeter user-session=pincabos-openbox autologin-user=pinball autologin-user-timeout=0 autologin-session=pincabos-openbox greeter-hide-users=true greeter-show-manual-login=false allow-guest=false EOF_LIGHTDM_AUTO cat >/etc/lightdm/lightdm.conf.d/50-pincabos.conf <<'EOF_LIGHTDM_PINCABOS' [Seat:*] greeter-session=lightdm-gtk-greeter user-session=pincabos-openbox autologin-user=pinball autologin-user-timeout=0 autologin-session=pincabos-openbox greeter-hide-users=true greeter-show-manual-login=false allow-guest=false EOF_LIGHTDM_PINCABOS chmod 644 /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/50-pincabos-autologin.conf /etc/lightdm/lightdm.conf.d/50-pincabos.conf if command -v systemctl >/dev/null 2>&1; then systemctl set-default graphical.target 2>/dev/null || true systemctl enable lightdm.service 2>/dev/null || true fi echo echo "=== Validation LightDM autologin 02 ===" id pinball || true groups pinball || true grep -RInE 'greeter-session|autologin-user|autologin-session|user-session|greeter-show-manual-login|greeter-hide-users|allow-guest' /etc/lightdm 2>/dev/null || true ls -lah /usr/share/xsessions/pincabos-openbox.desktop /usr/local/bin/pincabos-openbox-session 2>/dev/null || true } pco_ensure_lightdm_autologin_pinball_02 || true # === PINCABOS FIX 02 LIGHTDM AUTOLOGIN SAFETY END === # === PINCABOS FIX 02 RUN 99 CLEAN DEPOT FINAL START === pco_run_99_clean_depot_after_02() { local clean_script="/opt/pincabos/install/99-clean-depot.sh" echo pco_line 2>/dev/null || echo "----------------------------------------------------------------" printf "${ORANGE:-}PinCabOS - Cleanup final visible après 02-install-engine${RESET:-}\n" pco_line 2>/dev/null || echo "----------------------------------------------------------------" if [ ! -s "$clean_script" ]; then echo "WARN: cleanup absent ou vide: $clean_script" return 0 fi chmod +x "$clean_script" 2>/dev/null || true bash -n "$clean_script" if [ "$(id -u)" -eq 0 ]; then echo "Root détecté: lancement direct de 99-clean-depot.sh sans sudo." "$clean_script" else echo "WARN: non-root détecté, cleanup non lancé depuis 02." return 0 fi } pco_run_99_clean_depot_after_02 || true # === PINCABOS FIX 02 RUN 99 CLEAN DEPOT FINAL END ===