#!/usr/bin/env bash # =========================================================================== # FOSFENO :: instalador para Raspberry Pi OS Bookworm (Raspberry Pi 4 y 5) # # Uso: bash install.sh # instala todo (incluido projectM) # bash install.sh --no-projectm # omite la compilacion de projectM # bash install.sh --check # solo comprueba el sistema, no instala # =========================================================================== set -uo pipefail DIR="$(cd "$(dirname "$0")" && pwd)" source "$DIR/scripts/lib.sh" MODE="install" SKIP_PM="no" for arg in "$@"; do case "$arg" in --no-projectm) SKIP_PM="yes" ;; --check) MODE="check" ;; *) echo "Opcion desconocida: $arg"; exit 1 ;; esac done # Versiones minimas requeridas MIN_PYTHON="3.9" MIN_NODE="16" MIN_NPM="8" MIN_CMAKE="3.21" printf '%s\n' "$C_BLD===================================================" printf '%s\n' " FOSFENO :: instalador ($DIR)" printf '%s\n' "===================================================$C_RST" # --------------------------------------------------------------------------- # 1. Comprobacion del hardware y del sistema operativo # --------------------------------------------------------------------------- log_step "[1/9] Comprobando hardware y sistema operativo" MODELO="$(pi_model)" log_info "Modelo detectado: $MODELO" case "$MODELO" in *"Raspberry Pi 5"*) log_ok "Raspberry Pi 5 (soportada)" ;; *"Raspberry Pi 4"*) log_ok "Raspberry Pi 4 (soportada)" ;; *"Raspberry Pi"*) warn "Raspberry Pi distinta de 4/5: puede funcionar pero sin garantias" ;; *) warn "No parece una Raspberry Pi: continuando bajo tu responsabilidad" ;; esac if [ -r /etc/os-release ]; then . /etc/os-release log_info "Sistema: ${PRETTY_NAME:-desconocido}" if [ "${VERSION_CODENAME:-}" = "bookworm" ]; then log_ok "Raspberry Pi OS Bookworm" else warn "Se recomienda Raspberry Pi OS Bookworm (detectado: ${VERSION_CODENAME:-?})" fi fi ARCH="$(uname -m)" log_info "Arquitectura: $ARCH" [ "$ARCH" = "aarch64" ] && log_ok "Sistema de 64 bits" \ || warn "Se recomienda Raspberry Pi OS de 64 bits para mejor rendimiento" # --------------------------------------------------------------------------- # Modo --check: solo verifica lo que ya esta instalado y termina # --------------------------------------------------------------------------- if [ "$MODE" = "check" ]; then log_step "Comprobando herramientas ya instaladas" require_version "Python" "python3 --version" "$MIN_PYTHON" || true require_version "Node.js" "node --version" "$MIN_NODE" || true require_version "npm" "npm --version" "$MIN_NPM" || true require_version "CMake" "cmake --version" "$MIN_CMAKE" || true if need_cmd chromium-browser || need_cmd chromium; then log_ok "Chromium presente" else log_fail "Chromium no encontrado" fi need_cmd git && log_ok "git presente" || log_fail "git no encontrado" need_cmd projectMSDL && log_ok "projectM (projectMSDL) presente" \ || log_warn "projectM no instalado (opcional)" printf '\n' [ "$FOSFENO_WARNINGS" -eq 0 ] && log_ok "Comprobacion terminada sin avisos" \ || log_warn "Comprobacion terminada con $FOSFENO_WARNINGS aviso(s)" exit 0 fi # --------------------------------------------------------------------------- # 2. Paquetes del sistema # --------------------------------------------------------------------------- log_step "[2/9] Instalando dependencias del sistema (apt)" sudo apt-get update if sudo apt-get install -y \ python3 python3-venv python3-pip \ chromium-browser \ nodejs npm \ git cmake build-essential pkg-config \ libsdl2-dev libgles2-mesa-dev mesa-common-dev libglm-dev libpoco-dev \ pulseaudio-utils \ v4l-utils \ avahi-daemon \ xdotool unclutter; then log_ok "Paquetes apt instalados" else log_fail "Fallo al instalar paquetes apt" exit 1 fi # --------------------------------------------------------------------------- # 3. Verificacion de versiones de las herramientas # --------------------------------------------------------------------------- log_step "[3/9] Verificando versiones de las herramientas" require_version "Python" "python3 --version" "$MIN_PYTHON" \ || { log_fail "Python demasiado antiguo; aborto"; exit 1; } require_version "Node.js" "node --version" "$MIN_NODE" \ || warn "Node.js antiguo: 'npm install' podria fallar" require_version "npm" "npm --version" "$MIN_NPM" || true if [ "$SKIP_PM" = "no" ]; then require_version "CMake" "cmake --version" "$MIN_CMAKE" \ || { warn "CMake antiguo: projectM no se compilara"; SKIP_PM="yes"; } fi # --------------------------------------------------------------------------- # 4. Entorno Python # --------------------------------------------------------------------------- log_step "[4/9] Creando el entorno virtual de Python" python3 -m venv "$DIR/.venv" "$DIR/.venv/bin/pip" install --quiet --upgrade pip "$DIR/.venv/bin/pip" install --quiet -r "$DIR/backend/requirements.txt" if "$DIR/.venv/bin/python3" -c "import flask, flask_socketio" 2>/dev/null; then log_ok "Entorno Python listo (flask, flask-socketio)" else log_fail "Las dependencias de Python no se importan correctamente" exit 1 fi # --------------------------------------------------------------------------- # 5. Librerias web (Butterchurn, Hydra, Socket.IO, CodeMirror) # --------------------------------------------------------------------------- log_step "[5/9] Descargando librerias de visuales (npm)" cd "$DIR/web" if npm install --no-audit --no-fund --loglevel=error; then log_ok "Paquetes npm descargados" else log_fail "Fallo en 'npm install'" exit 1 fi mkdir -p "$DIR/web/lib/codemirror" # Copia un fichero y comprueba que existe y no esta vacio copy_lib() { # copy_lib if cp "$1" "$2" 2>/dev/null && [ -s "$2" ]; then log_ok "$3" else warn "no se pudo copiar: $3 ($1)" fi } copy_lib node_modules/butterchurn/dist/butterchurn.min.js \ lib/butterchurn.min.js "Butterchurn" copy_lib node_modules/butterchurn-presets/dist/base.min.js \ lib/butterchurn-presets.min.js "Presets de Butterchurn" copy_lib node_modules/socket.io-client/dist/socket.io.min.js \ lib/socket.io.min.js "Socket.IO" copy_lib node_modules/qrcode-generator/qrcode.js \ lib/qrcode.js "Generador de codigo QR" copy_lib node_modules/codemirror/lib/codemirror.js \ lib/codemirror/codemirror.js "CodeMirror (editor)" copy_lib node_modules/codemirror/lib/codemirror.css \ lib/codemirror/codemirror.css "CodeMirror (estilos)" copy_lib node_modules/codemirror/mode/javascript/javascript.js \ lib/codemirror/javascript.js "CodeMirror (modo JavaScript)" copy_lib node_modules/codemirror/mode/clike/clike.js \ lib/codemirror/clike.js "CodeMirror (modo GLSL)" copy_lib node_modules/codemirror/theme/material-darker.css \ lib/codemirror/material-darker.css "CodeMirror (tema)" # Hydra no siempre ubica el build en el mismo sitio: probamos varias rutas if cp node_modules/hydra-synth/dist/hydra-synth.js lib/hydra-synth.js 2>/dev/null && [ -s lib/hydra-synth.js ]; then log_ok "Hydra" elif cp node_modules/hydra-synth/build/hydra-synth.js lib/hydra-synth.js 2>/dev/null && [ -s lib/hydra-synth.js ]; then log_ok "Hydra (build/)" else warn "no se encontro el build de hydra-synth en node_modules; revisa la ruta" fi cd "$DIR" # --------------------------------------------------------------------------- # 6. projectM nativo (compilado desde fuente) # --------------------------------------------------------------------------- log_step "[6/9] projectM (visualizador nativo)" if [ "$SKIP_PM" = "yes" ]; then log_info "projectM omitido" elif command -v projectMSDL >/dev/null 2>&1; then log_ok "projectM ya estaba instalado ($(command -v projectMSDL))" else log_info "Compilando projectM desde fuente (puede tardar 10-20 min en una Pi)..." if bash "$DIR/scripts/build-projectm.sh"; then command -v projectMSDL >/dev/null 2>&1 \ && log_ok "projectM compilado e instalado" \ || warn "projectM compilo pero 'projectMSDL' no esta en el PATH" else warn "projectM no se pudo compilar; FOSFENO seguira con Butterchurn, Hydra y Shaders" fi fi # --------------------------------------------------------------------------- # 7. Presets de projectM y carpeta de videos # --------------------------------------------------------------------------- log_step "[7/9] Recursos (presets de projectM, carpeta de videos)" mkdir -p "$DIR/data/presets-projectm" "$DIR/data/videos" if [ -z "$(ls -A "$DIR/data/presets-projectm" 2>/dev/null)" ]; then if git clone --depth 1 \ https://github.com/projectM-visualizer/presets-cream-of-the-crop.git \ "$DIR/data/presets-projectm" 2>/dev/null; then N=$(find "$DIR/data/presets-projectm" -name '*.milk' | wc -l) log_ok "Presets de projectM descargados ($N presets)" else warn "no se pudieron descargar los presets de projectM" fi else log_ok "Presets de projectM ya presentes" fi log_info "Copia tus clips .mp4 en: $DIR/data/videos/" # --------------------------------------------------------------------------- # 8. Arranque automatico y permisos # --------------------------------------------------------------------------- log_step "[8/9] Configurando arranque automatico y permisos" chmod +x "$DIR/scripts/"*.sh "$DIR/install.sh" "$DIR/uninstall.sh" mkdir -p "$HOME/.config/autostart" if sed "s#__DIR__#$DIR#g" "$DIR/scripts/fosfeno-autostart.desktop" \ > "$HOME/.config/autostart/fosfeno.desktop"; then log_ok "Arranque automatico configurado (~/.config/autostart/fosfeno.desktop)" fi # Nombre de red fijo: deja el panel accesible en http://.local/ HOSTNAME_WANT="$(python3 -c "import json;print(json.load(open('$DIR/config.json')).get('network',{}).get('hostname','fosfeno'))" 2>/dev/null || echo fosfeno)" if [ "$(hostname)" != "$HOSTNAME_WANT" ]; then sudo hostnamectl set-hostname "$HOSTNAME_WANT" 2>/dev/null || true if grep -q "^127.0.1.1" /etc/hosts; then sudo sed -i "s/^127.0.1.1.*/127.0.1.1\t$HOSTNAME_WANT/" /etc/hosts else echo -e "127.0.1.1\t$HOSTNAME_WANT" | sudo tee -a /etc/hosts >/dev/null fi log_ok "Nombre de red puesto a '$HOSTNAME_WANT' (panel en http://$HOSTNAME_WANT.local/)" else log_ok "Nombre de red: $HOSTNAME_WANT" fi if echo "$USER ALL=(ALL) NOPASSWD: /sbin/reboot, /sbin/poweroff" \ | sudo tee /etc/sudoers.d/fosfeno >/dev/null \ && sudo chmod 440 /etc/sudoers.d/fosfeno; then log_ok "Permisos de reinicio/apagado configurados" fi # --------------------------------------------------------------------------- # 9. Permiso para el puerto 80 # --------------------------------------------------------------------------- log_step "[9/9] Permitiendo a Python escuchar en el puerto 80" PYBIN="$(readlink -f "$DIR/.venv/bin/python3")" if sudo setcap 'cap_net_bind_service=+ep' "$PYBIN" 2>/dev/null; then log_ok "Puerto 80 habilitado" else warn "no se pudo habilitar el puerto 80; cambia 'server.port' a 8080 en config.json" fi # --------------------------------------------------------------------------- # Resumen final # --------------------------------------------------------------------------- printf '\n%s\n' "$C_BLD===================================================" if [ "$FOSFENO_WARNINGS" -eq 0 ]; then log_ok "FOSFENO instalado correctamente, sin avisos." else log_warn "FOSFENO instalado con $FOSFENO_WARNINGS aviso(s) (revisa arriba)." fi printf '%s\n' "===================================================$C_RST" cat < System Options -> Boot / Auto Login -> Desktop Autologin 2. Reinicia: sudo reboot Como entrar en el panel de control: - Al arrancar, el proyector muestra un codigo QR y la direccion. Escanea el QR con el movil y se abre el panel. Mas facil imposible. - Si prefieres escribirla: http://$HOSTNAME_WANT.local/ - El movil debe estar en la misma red (WiFi o cable) que la Raspberry. Para volver a comprobar el sistema sin reinstalar: bash install.sh --check EOF