fosfeno/install.sh
hacklab 6df128a377 Soporte multi-distro en portatil, titulos mas grandes, panel mas ancho y refresco de microfono
install.sh --laptop ahora detecta el gestor de paquetes (apt, dnf, pacman o zypper) e instala las dependencias en Debian/Ubuntu/Mint, Fedora, Arch/Manjaro y openSUSE; en el resto avisa de los 5 paquetes a instalar a mano. En portatil no se compila projectM (opcional). Panel: titulo superior mas grande y cada titulo de tarjeta mas grande y en fuente Xirod; en pantalla ancha el panel ocupa el 94% (hasta 1600px) para aprovechar el portatil. Audio: nuevo boton 'Aplicar microfono' que reconecta la captura desde el panel (evento reacquire_audio); el microfono integrado del portatil se capta como entrada por defecto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 18:11:01 +02:00

427 lines
18 KiB
Bash
Executable file

#!/usr/bin/env bash
# ===========================================================================
# FOSFENO :: instalador para Raspberry Pi OS Bookworm (Raspberry Pi 4 y 5)
#
# Uso: bash install.sh # Raspberry Pi: instala todo
# bash install.sh --laptop # portatil Linux (cualquier distro)
# 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"
LAPTOP="no"
for arg in "$@"; do
case "$arg" in
--no-projectm) SKIP_PM="yes" ;;
--check) MODE="check" ;;
--laptop) LAPTOP="yes" ;;
*) 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 el sistema"
if [ -r /etc/os-release ]; then
. /etc/os-release
log_info "Sistema: ${PRETTY_NAME:-desconocido}"
fi
if [ "$LAPTOP" = "yes" ]; then
log_ok "Modo portatil (Linux de escritorio)"
else
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. Si es un portatil, usa: bash install.sh --laptop" ;;
esac
if [ "${VERSION_CODENAME:-}" != "bookworm" ]; then
warn "Se recomienda Raspberry Pi OS Bookworm (detectado: ${VERSION_CODENAME:-?})"
fi
fi
# ---------------------------------------------------------------------------
# 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
# ---------------------------------------------------------------------------
# Instala las dependencias en un portatil. Soporta apt, dnf, pacman y zypper,
# de modo que FOSFENO corre en Debian/Ubuntu/Mint, Fedora, Arch/Manjaro y
# openSUSE. En portatil solo hace falta un nucleo pequeno (Python, Node, git
# y Chromium): los motores de visuales son web y no necesitan nada mas.
install_laptop_deps() {
local mgr=""
if need_cmd apt-get; then mgr="apt"
elif need_cmd dnf; then mgr="dnf"
elif need_cmd pacman; then mgr="pacman"
elif need_cmd zypper; then mgr="zypper"
else
log_fail "No reconozco el gestor de paquetes de tu distribucion."
log_fail "Instala a mano y reintenta: python3 (con venv), nodejs, npm,"
log_fail "git y chromium."
return 1
fi
log_info "Gestor de paquetes detectado: $mgr"
# --- Python y git ---
case "$mgr" in
apt) sudo apt-get update
sudo apt-get install -y python3 python3-venv python3-pip git ;;
dnf) sudo dnf install -y python3 python3-pip git ;;
pacman) sudo pacman -Sy --needed --noconfirm python python-pip git ;;
zypper) sudo zypper --non-interactive install python3 python3-pip git ;;
esac || { log_fail "no se pudieron instalar Python y git"; return 1; }
log_ok "Python y git instalados"
# --- Node.js y npm: solo si faltan (respeta NodeSource, nvm, etc.) ---
if need_cmd node && need_cmd npm; then
log_ok "Node.js y npm ya estaban ($(node --version 2>/dev/null))"
else
case "$mgr" in
apt) sudo apt-get install -y nodejs npm ;;
dnf) sudo dnf install -y nodejs npm ;;
pacman) sudo pacman -S --needed --noconfirm nodejs npm ;;
zypper) sudo zypper --non-interactive install nodejs npm ;;
esac
need_cmd node && need_cmd npm \
&& log_ok "Node.js y npm instalados" \
|| warn "no se pudo instalar Node.js; instalalo a mano y reintenta"
fi
# --- Chromium ---
if need_cmd chromium || need_cmd chromium-browser || need_cmd google-chrome; then
log_ok "Chromium ya estaba instalado"
else
case "$mgr" in
apt) sudo apt-get install -y chromium 2>/dev/null \
|| sudo apt-get install -y chromium-browser ;;
dnf) sudo dnf install -y chromium ;;
pacman) sudo pacman -S --needed --noconfirm chromium ;;
zypper) sudo zypper --non-interactive install chromium ;;
esac
need_cmd chromium || need_cmd chromium-browser \
&& log_ok "Chromium instalado" \
|| warn "no se pudo instalar Chromium; instalalo a mano (chromium o Chrome)"
fi
# --- avahi: opcional, permite llegar al panel por nombre .local ---
case "$mgr" in
apt) sudo apt-get install -y avahi-daemon 2>/dev/null || true ;;
dnf) sudo dnf install -y avahi 2>/dev/null || true ;;
pacman) sudo pacman -S --needed --noconfirm avahi 2>/dev/null || true ;;
zypper) sudo zypper --non-interactive install avahi 2>/dev/null || true ;;
esac
return 0
}
log_step "[2/9] Instalando dependencias del sistema"
if [ "$LAPTOP" = "yes" ]; then
install_laptop_deps || { log_fail "Faltan dependencias; aborto."; exit 1; }
else
sudo apt-get update
if sudo apt-get install -y \
python3 python3-venv python3-pip \
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
# Node.js y npm: si ya estan (p.ej. NodeSource) no se tocan, porque el
# paquete 'npm' de Debian choca con el 'nodejs' de NodeSource.
if need_cmd node && need_cmd npm; then
log_ok "Node.js y npm ya estaban instalados ($(node --version 2>/dev/null))"
elif need_cmd node; then
warn "Node.js esta, pero falta npm; instalalo con el mismo metodo que node"
elif sudo apt-get install -y nodejs npm 2>/dev/null; then
log_ok "Node.js y npm instalados"
else
warn "no se pudo instalar Node.js; instalalo a mano y reintenta"
fi
# Chromium: 'chromium' o 'chromium-browser' segun la version del sistema.
if need_cmd chromium || need_cmd chromium-browser; then
log_ok "Chromium ya estaba instalado"
elif sudo apt-get install -y chromium 2>/dev/null; then
log_ok "Chromium instalado (paquete 'chromium')"
elif sudo apt-get install -y chromium-browser 2>/dev/null; then
log_ok "Chromium instalado (paquete 'chromium-browser')"
else
warn "no se pudo instalar Chromium automaticamente; instalalo a mano"
fi
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" ] && [ "$LAPTOP" = "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 <origen> <destino> <descripcion>
if cp "$1" "$2" 2>/dev/null && [ -s "$2" ]; then
log_ok "$3"
else
warn "no se pudo copiar: $3 ($1)"
fi
}
# Busca un fichero por nombre dentro de un paquete y lo copia. Es robusto a
# que el paquete cambie de carpeta (dist/, lib/, build/) entre versiones.
find_copy() { # find_copy <paquete> <nombre> <destino> <descripcion>
local f
f="$(find "node_modules/$1" -name "$2" -type f 2>/dev/null | head -n1)"
if [ -n "$f" ] && cp "$f" "$3" 2>/dev/null && [ -s "$3" ]; then
log_ok "$4"
else
warn "no se encontro $4 ($2 dentro de node_modules/$1)"
fi
}
find_copy butterchurn butterchurn.min.js lib/butterchurn.min.js "Butterchurn"
find_copy butterchurn-presets butterchurnPresets.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 command -v projectMSDL >/dev/null 2>&1; then
log_ok "projectM ya estaba instalado ($(command -v projectMSDL))"
elif [ "$LAPTOP" = "yes" ]; then
log_info "projectM nativo no instalado (es opcional)."
log_info "Los motores Butterchurn, Hydra, Shaders y Mezclador funcionan sin el."
log_info "Si lo quieres, instala el paquete 'projectm' de tu distribucion."
elif [ "$SKIP_PM" = "yes" ]; then
log_info "projectM omitido"
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] Arranque automatico y permisos"
chmod +x "$DIR/scripts/"*.sh "$DIR/install.sh" "$DIR/uninstall.sh" 2>/dev/null
[ -f "$DIR/fosfeno" ] && chmod +x "$DIR/fosfeno"
HOSTNAME_WANT="$(hostname)"
if [ "$LAPTOP" = "yes" ]; then
log_info "Modo portatil: sin arranque automatico ni cambios en el sistema."
log_info "FOSFENO se lanza a mano con ./fosfeno cuando quieras usarlo."
else
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://<hostname>.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
fi
# ---------------------------------------------------------------------------
# 9. Acceso de red
# ---------------------------------------------------------------------------
log_step "[9/9] Acceso de red"
if [ "$LAPTOP" = "yes" ]; then
log_info "Modo portatil: el panel usara el puerto 8080, no hace falta nada mas."
else
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
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"
if [ "$LAPTOP" = "yes" ]; then
cat <<EOF
FOSFENO instalado en modo portatil.
Para arrancarlo, desde esta carpeta:
./fosfeno
Se abrira el panel de control en el navegador y una ventana aparte con
las visuales (arrastrala al proyector y pulsa F11 para pantalla completa).
Para cerrarlo todo, pulsa Ctrl+C en la terminal.
Para comprobar el sistema sin reinstalar:
bash install.sh --check
EOF
else
cat <<EOF
Siguientes pasos:
1. Activa el login automatico al escritorio:
sudo raspi-config -> 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
fi