#!/bin/bash # package-deb.sh — Empaqueta G Radio Player como .deb instalable # Uso: ./package-deb.sh [--target-dir ] [--suffix ] # --target-dir directorio de binarios (default: target/release) # --suffix sufijo al nombre del paquete (ej: _bookworm) set -e VERSION="${1:-0.2.8}" TARGET_DIR="target/release" SUFFIX="" shift || true while [[ $# -gt 0 ]]; do case "$1" in --target-dir) TARGET_DIR="$2"; shift 2 ;; --suffix) SUFFIX="$2"; shift 2 ;; *) shift ;; esac done ARCH="$(dpkg --print-architecture)" PKG_NAME="gradio-player" PKG_DIR="${PKG_NAME}_${VERSION}_${ARCH}${SUFFIX}" ROOT="$(cd "$(dirname "$0")" && pwd)" # ── Verificar que los binarios existen ──────────────────────────────────────── for BIN in radio-player comercial-scheduler playlist-refill \ gr-buscador gr-playlist gr-pautaje gr-parrilla \ gr-botonera gr-visor gr-reportes gr-record; do if [ ! -f "$ROOT/$TARGET_DIR/$BIN" ]; then echo "[!] Binario faltante: $TARGET_DIR/$BIN" echo " Ejecuta primero: cargo build --release" exit 1 fi done echo "========================================" echo " Empaquetando $PKG_NAME $VERSION ($ARCH)" echo "========================================" # ── Limpiar directorio anterior ─────────────────────────────────────────────── rm -rf "$PKG_DIR" # ── Verificar que existe la carpeta Time ────────────────────────────────────── if [ -d "$ROOT/assets/Time" ]; then TIME_SRC="$ROOT/assets/Time" elif [ -d "$ROOT/target/data/panel/Time" ]; then TIME_SRC="$ROOT/target/data/panel/Time" else echo "[!] Carpeta Time no encontrada (assets/Time ni target/data/panel/Time)" exit 1 fi # ── Estructura de directorios ───────────────────────────────────────────────── mkdir -p "$PKG_DIR/DEBIAN" mkdir -p "$PKG_DIR/usr/local/bin" mkdir -p "$PKG_DIR/usr/local/share/radio-player/Time" mkdir -p "$PKG_DIR/usr/local/share/radio-player/opencode/agents" mkdir -p "$PKG_DIR/usr/local/share/radio-player/opencode/context" mkdir -p "$PKG_DIR/usr/local/share/radio-player/skins" mkdir -p "$PKG_DIR/usr/share/applications" mkdir -p "$PKG_DIR/usr/share/icons/hicolor/48x48/apps" # ── Copiar archivos de hora (Time) ─────────────────────────────────────────── echo "[*] Copiando carpeta Time..." cp "$TIME_SRC/"* "$PKG_DIR/usr/local/share/radio-player/Time/" # ── Copiar contexto de IA (opencode) ───────────────────────────────────────── if [ -d "$ROOT/assets/opencode" ]; then echo "[*] Copiando contexto IA (opencode)..." cp "$ROOT/assets/opencode/AGENTS.md" "$PKG_DIR/usr/local/share/radio-player/opencode/" cp "$ROOT/assets/opencode/opencode.jsonc" "$PKG_DIR/usr/local/share/radio-player/opencode/" 2>/dev/null || true cp "$ROOT/assets/opencode/agents/"* "$PKG_DIR/usr/local/share/radio-player/opencode/agents/" cp "$ROOT/assets/opencode/context/"* "$PKG_DIR/usr/local/share/radio-player/opencode/context/" fi # ── Copiar skins de ejemplo ─────────────────────────────────────────────────── if [ -d "$ROOT/assets/skins" ]; then echo "[*] Copiando skins de ejemplo..." cp -r "$ROOT/assets/skins/"* "$PKG_DIR/usr/local/share/radio-player/skins/" 2>/dev/null || true fi # ── Copiar binarios ─────────────────────────────────────────────────────────── echo "[*] Copiando binarios..." for BIN in radio-player comercial-scheduler playlist-refill \ gr-buscador gr-playlist gr-pautaje gr-parrilla \ gr-botonera gr-visor gr-reportes gr-record; do cp "$ROOT/$TARGET_DIR/$BIN" "$PKG_DIR/usr/local/bin/$BIN" chmod 755 "$PKG_DIR/usr/local/bin/$BIN" done # ── Lanzador principal ──────────────────────────────────────────────────────── echo "[*] Creando lanzador gradio.sh..." cat > "$PKG_DIR/usr/local/bin/gradio.sh" << 'LAUNCHER' #!/bin/bash # Lanzador de G Radio Player — equivalente al run.sh del proyecto fuente BIN="/usr/local/bin" HOME_GRADIO="$HOME/.gradio/data" PIDDIR="$HOME_GRADIO/tmp/pids" mkdir -p "$PIDDIR" # URL del relay internet (usada por relay.rs si "relay habilitado" está activo # en Configuración). Sin esto, relay.rs cae al placeholder wss://relay.example.com/ws # y jamás llega a registrarse — la conexión remota vía Internet queda rota en silencio. export GRADIO_RELAY_URL="${GRADIO_RELAY_URL:-wss://relay.gradio.net/ws}" # ── Matar proceso por PID guardado + búsqueda por nombre ───────────────────── kill_aux() { local name="$1" local pidfile="$PIDDIR/$name.pid" if [ -f "$pidfile" ]; then local pid pid=$(cat "$pidfile" 2>/dev/null) if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then echo " Deteniendo $name (PID $pid)..." kill "$pid" 2>/dev/null for i in $(seq 1 6); do sleep 0.5 kill -0 "$pid" 2>/dev/null || break done kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null fi rm -f "$pidfile" fi pkill -x "$name" 2>/dev/null sleep 0.3 } # ── Matar instancias previas ────────────────────────────────────────────────── echo "==> Deteniendo instancias previas (si las hay)..." kill_aux "comercial-scheduler" kill_aux "playlist-refill" sleep 0.5 # ── Crear estructura de directorios ────────────────────────────────────────── mkdir -p "$HOME_GRADIO/tmp" mkdir -p "$HOME_GRADIO/comerciales" mkdir -p "$HOME_GRADIO/eventos-espera" mkdir -p "$HOME_GRADIO/eventos" mkdir -p "$HOME_GRADIO/parrilla" mkdir -p "$HOME_GRADIO/reporte" mkdir -p "$HOME_GRADIO/panel" mkdir -p "$HOME/G Radio/inicio-espacio-pub" mkdir -p "$HOME/G Radio/fin-espacio-pub" mkdir -p "$HOME/G Radio/comerciales" # ── Instalar carpeta Time (audios de hora) si aún no existe ────────────────── if [ ! -d "$HOME_GRADIO/panel/Time" ]; then echo "==> Instalando carpeta Time en $HOME_GRADIO/panel/..." cp -r /usr/local/share/radio-player/Time "$HOME_GRADIO/panel/Time" fi # ── Instalar contexto IA (opencode) si no existe o está desactualizado ─────── OPENCODE_SRC="/usr/local/share/radio-player/opencode" OPENCODE_DST="$HOME_GRADIO/opencode" if [ -d "$OPENCODE_SRC" ] && [ ! -f "$OPENCODE_DST/AGENTS.md" ]; then echo "==> Instalando contexto IA en $OPENCODE_DST/..." mkdir -p "$OPENCODE_DST/agents" "$OPENCODE_DST/context" cp -r "$OPENCODE_SRC/"* "$OPENCODE_DST/" fi # ── Instalar skins de ejemplo (solo los que aún no existan) ────────────────── SKINS_SRC="/usr/local/share/radio-player/skins" SKINS_DST="$HOME_GRADIO/skins" if [ -d "$SKINS_SRC" ]; then mkdir -p "$SKINS_DST" for skin_dir in "$SKINS_SRC"/*/; do [ -d "$skin_dir" ] || continue skin_name=$(basename "$skin_dir") if [ ! -d "$SKINS_DST/$skin_name" ]; then echo "==> Instalando skin '$skin_name' en $SKINS_DST/..." cp -r "$skin_dir" "$SKINS_DST/$skin_name" fi done fi # ── Migración device → gradio.config (compatibilidad con versiones anteriores) DEVICE_FILE="$HOME_GRADIO/tmp/device" CONFIG_FILE="$HOME_GRADIO/tmp/gradio.config" if [ -f "$DEVICE_FILE" ] && [ ! -f "$CONFIG_FILE" ]; then echo "==> Migrando device → gradio.config..." MIX=$(cat "$HOME_GRADIO/mix" 2>/dev/null || echo "3.0") paste -d"\n" "$DEVICE_FILE" /dev/null > "$CONFIG_FILE" printf "G Radio\n%s\n" "$MIX" >> "$CONFIG_FILE" rm -f "$DEVICE_FILE" fi # ── Archivos de configuración por defecto ───────────────────────────────────── [ -f "$HOME_GRADIO/tmp/upvol" ] || echo "90" > "$HOME_GRADIO/tmp/upvol" [ -f "$HOME_GRADIO/tmp/downvol" ] || echo "20" > "$HOME_GRADIO/tmp/downvol" [ -f "$HOME_GRADIO/mix" ] || echo "3" > "$HOME_GRADIO/mix" # ── Limpiar listas temporales (toma la programación de la hora actual) ──────── > "$HOME_GRADIO/tmp/comercialeslist4" > "$HOME_GRADIO/tmp/eventos-esperalist" > "$HOME_GRADIO/tmp/eventoslist" > "$HOME_GRADIO/tmp/playlist4" echo "==> Listas temporales limpiadas." # ── Iniciar daemons auxiliares ──────────────────────────────────────────────── echo "==> Iniciando comercial-scheduler..." "$BIN/comercial-scheduler" & SCHED_PID=$! echo "$SCHED_PID" > "$PIDDIR/comercial-scheduler.pid" echo " PID: $SCHED_PID" echo "==> Iniciando playlist-refill..." "$BIN/playlist-refill" & REFILL_PID=$! echo "$REFILL_PID" > "$PIDDIR/playlist-refill.pid" echo " PID: $REFILL_PID" # ── Watchdog: verifica cada 30s que los daemons siguen vivos ────────────────── watchdog() { while true; do sleep 30 kill -0 "$RADIO_PID" 2>/dev/null || break if ! kill -0 "$SCHED_PID" 2>/dev/null; then echo "==> WATCHDOG: comercial-scheduler caído, reiniciando..." "$BIN/comercial-scheduler" & SCHED_PID=$! echo "$SCHED_PID" > "$PIDDIR/comercial-scheduler.pid" fi if ! kill -0 "$REFILL_PID" 2>/dev/null; then echo "==> WATCHDOG: playlist-refill caído, reiniciando..." "$BIN/playlist-refill" & REFILL_PID=$! echo "$REFILL_PID" > "$PIDDIR/playlist-refill.pid" fi done } # ── Inhibir suspensión / DPMS (ARM: previene congelamiento del main loop GTK4) ── if command -v systemd-inhibit &>/dev/null; then echo "==> Inhibiendo idle con systemd-inhibit..." RUST_LOG=info systemd-inhibit \ --what=idle \ --who="G Radio Player" \ --why="Reproducción de audio en curso" \ --mode=block \ "$BIN/radio-player" "$@" & RADIO_PID=$! else xset s off 2>/dev/null || true xset -dpms 2>/dev/null || true xset s noblank 2>/dev/null || true echo "==> Iniciando radio-player..." RUST_LOG=info "$BIN/radio-player" "$@" & RADIO_PID=$! fi watchdog & WATCHDOG_PID=$! wait "$RADIO_PID" # ── Limpieza al salir ───────────────────────────────────────────────────────── echo "==> Cerrando procesos auxiliares..." kill "$WATCHDOG_PID" 2>/dev/null kill_aux "comercial-scheduler" kill_aux "playlist-refill" rm -f "$PIDDIR/comercial-scheduler.pid" "$PIDDIR/playlist-refill.pid" echo "==> Terminado." LAUNCHER chmod 755 "$PKG_DIR/usr/local/bin/gradio.sh" # ── Icono ───────────────────────────────────────────────────────────────────── if [ -f "$ROOT/assets/gradio.png" ]; then cp "$ROOT/assets/gradio.png" "$PKG_DIR/usr/share/icons/hicolor/48x48/apps/gradio-player.png" chmod 644 "$PKG_DIR/usr/share/icons/hicolor/48x48/apps/gradio-player.png" fi # ── Entrada de escritorio ───────────────────────────────────────────────────── cat > "$PKG_DIR/usr/share/applications/com.gradio.radio-player.desktop" << DESKTOP [Desktop Entry] Type=Application Name=G Radio Player GenericName=Radio Automation Comment=Sistema de automatización de radio profesional Exec=/usr/local/bin/gradio.sh Icon=gradio-player Categories=AudioVideo;Audio;Player; Terminal=false StartupNotify=true Keywords=radio;audio;player;automation; DESKTOP # ── DEBIAN/control ──────────────────────────────────────────────────────────── echo "[*] Generando DEBIAN/control..." INSTALLED_SIZE=$(du -sk "$PKG_DIR/usr" | cut -f1) cat > "$PKG_DIR/DEBIAN/control" << CONTROL Package: gradio-player Version: ${VERSION} Architecture: ${ARCH} Maintainer: G Radio Installed-Size: ${INSTALLED_SIZE} Depends: libgtk-4-1 (>= 4.6), libgstreamer1.0-0 (>= 1.20), libgstreamer-plugins-base1.0-0 (>= 1.20), gstreamer1.0-plugins-base (>= 1.20), gstreamer1.0-plugins-good (>= 1.20), gstreamer1.0-plugins-bad (>= 1.20), gstreamer1.0-plugins-ugly (>= 1.20), gstreamer1.0-libav (>= 1.20), gstreamer1.0-alsa | gstreamer1.0-pulseaudio, ffmpeg, libc6 (>= 2.17) Section: sound Priority: optional Description: G Radio Player — sistema de automatización de radio Reproductor profesional para emisoras de radio con programación automática de música, comerciales y eventos. Incluye interfaz GTK4, crossfading, duck de volumen y gestión de parrilla horaria. . Componentes: radio-player, comercial-scheduler, playlist-refill, gr-pautaje, gr-parrilla, gr-botonera, gr-visor, gr-buscador, gr-playlist, gr-record. CONTROL # ── DEBIAN/postinst ─────────────────────────────────────────────────────────── cat > "$PKG_DIR/DEBIAN/postinst" << 'POSTINST' #!/bin/bash set -e # Limpiar entrada de escritorio de versiones antiguas (renombrada a # com.gradio.radio-player.desktop para que el app_id coincida con el nombre # del .desktop — requerido para que Wayland resuelva el ícono correctamente). rm -f /usr/share/applications/gradio-player.desktop # Actualizar caché de iconos y menú de escritorio if command -v gtk-update-icon-cache &>/dev/null; then gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true fi if command -v update-desktop-database &>/dev/null; then update-desktop-database /usr/share/applications 2>/dev/null || true fi echo "" echo "G Radio Player instalado correctamente." echo "Ejecutar: gradio.sh o buscar 'G Radio Player' en el menú." POSTINST chmod 755 "$PKG_DIR/DEBIAN/postinst" # ── DEBIAN/prerm ────────────────────────────────────────────────────────────── # No matamos procesos aquí: Linux mantiene el ejecutable anterior en memoria # hasta que cierre (safe inode replacement). Tener pkill en prerm rompe dpkg # al actualizar porque el prerm de la versión INSTALADA corre primero — y si # esa versión tenía pkill -f (bug histórico), mata al propio dpkg. cat > "$PKG_DIR/DEBIAN/prerm" << 'PRERM' #!/bin/bash exit 0 PRERM chmod 755 "$PKG_DIR/DEBIAN/prerm" # ── DEBIAN/postrm ───────────────────────────────────────────────────────────── cat > "$PKG_DIR/DEBIAN/postrm" << 'POSTRM' #!/bin/bash # Solo borrar archivos en remove/purge — nunca en upgrade/failed-upgrade. # Durante un upgrade dpkg instala el nuevo paquete ANTES de correr este # postrm; si borramos incondicionalmente, eliminamos los binarios recién # instalados por la versión nueva. case "$1" in remove|purge) for BIN in radio-player comercial-scheduler playlist-refill \ gradio.sh gr-buscador gr-playlist gr-pautaje gr-parrilla \ gr-botonera gr-visor gr-reportes gr-record; do rm -f "/usr/local/bin/$BIN" done rm -rf /usr/local/share/radio-player rm -f /usr/share/applications/com.gradio.radio-player.desktop rm -f /usr/share/applications/gradio-player.desktop # limpieza de versiones antiguas rm -f /usr/share/icons/hicolor/48x48/apps/gradio-player.png command -v gtk-update-icon-cache &>/dev/null && \ gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true command -v update-desktop-database &>/dev/null && \ update-desktop-database /usr/share/applications 2>/dev/null || true ;; esac POSTRM chmod 755 "$PKG_DIR/DEBIAN/postrm" # ── Construir el .deb ───────────────────────────────────────────────────────── echo "[*] Construyendo paquete .deb..." dpkg-deb --build --root-owner-group "$PKG_DIR" DEB_FILE="${PKG_DIR}.deb" SIZE=$(du -sh "$DEB_FILE" | cut -f1) echo "" echo "========================================" echo " Paquete listo: $DEB_FILE ($SIZE)" echo "========================================" echo "" echo " Instalar: sudo dpkg -i $DEB_FILE" echo " O con apt: sudo apt install ./$DEB_FILE" echo "" echo " Desinstalar: sudo apt remove gradio-player" echo ""