fosfeno/install.sh
hacklab 3c1c631895 Modo portatil: ejecutar FOSFENO en un portatil Linux
Anade 'bash install.sh --laptop' y el lanzador './fosfeno' para correr FOSFENO en portatiles Debian/Ubuntu/Mint sin Raspberry Pi: puerto 8080, sin arranque automatico ni cambios en el sistema. El servidor admite las variables FOSFENO_PORT y FOSFENO_NO_KIOSK. Nueva documentacion en docs/portatil.md.

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

324 lines
13 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 (Debian/Ubuntu/Mint)
# 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)"
if ! need_cmd apt-get; then
log_fail "El modo --laptop usa apt (Debian, Ubuntu o Mint)."
log_fail "Tu sistema no tiene apt; este instalador no sirve aqui."
exit 1
fi
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
# ---------------------------------------------------------------------------
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 <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
}
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] 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