#!/bin/bash set -e # build-novela.sh - Compila y publica una novela visual en NovelasVM # Uso: build-novela.sh [motor] # motor: renpy | unity | web | onscripter (si no se indica, se detecta automaticamente) # Ejemplo: build-novela.sh demo /opt/novelas/projects/demo NAME="$1" PROJECT="$2" FORCE_ENGINE="$3" RENPY_HOME="/opt/novelas/tools/renpy" BUILDS_DIR="/opt/novelas/builds" WWW_ROOT="/var/www/novelas" WWW_GAMES="$WWW_ROOT/games" NGINX_SNIPPETS_DIR="/etc/nginx/snippets/novelas-games" META_FILE="$PROJECT/meta.json" # ----------------------------------------------------------------------------- # Validaciones basicas # ----------------------------------------------------------------------------- if [ -z "$NAME" ] || [ -z "$PROJECT" ]; then echo "Uso: $0 [renpy|unity|web|onscripter]" exit 1 fi if [ ! -d "$PROJECT" ]; then echo "ERROR: No existe el proyecto $PROJECT" exit 1 fi # Slug seguro: minusculas, numeros, guiones SAFE_NAME=$(echo "$NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') if [ -z "$SAFE_NAME" ]; then echo "ERROR: El slug '$NAME' no es valido" exit 1 fi # ----------------------------------------------------------------------------- # Deteccion / validacion de motor # ----------------------------------------------------------------------------- if [ -n "$FORCE_ENGINE" ]; then ENGINE="$FORCE_ENGINE" echo "[*] Motor forzado: $ENGINE" else ENGINE=$(/opt/novelas/bin/detect-engine.sh "$PROJECT") echo "[*] Motor detectado: $ENGINE" fi case "$ENGINE" in renpy|unity|web|onscripter) ;; *) echo "ERROR: Motor no soportado: $ENGINE" echo "Estructura no reconocida. Se esperaba:" echo " - renpy: project.json o game/script.rpy" echo " - unity: Build/ + TemplateData/ + index.html" echo " - web: index.html" echo " - onscripter: 0.txt/nscript.dat, ons.cfg o onscripter*.exe" exit 1 ;; esac # ----------------------------------------------------------------------------- # Validacion especifica por motor # ----------------------------------------------------------------------------- if [ "$ENGINE" = "unity" ]; then if [ ! -d "$PROJECT/Build" ] || [ ! -d "$PROJECT/TemplateData" ] || [ ! -f "$PROJECT/index.html" ]; then echo "ERROR: El proyecto Unity WebGL no tiene la estructura esperada (Build/, TemplateData/, index.html)" exit 1 fi fi if [ "$ENGINE" = "web" ]; then if [ ! -f "$PROJECT/index.html" ]; then echo "ERROR: El proyecto web no contiene index.html" exit 1 fi fi if [ "$ENGINE" = "onscripter" ]; then if [ ! -f "$PROJECT/0.txt" ] && [ ! -f "$PROJECT/nscript.dat" ] && [ ! -f "$PROJECT/ons.cfg" ] && [ ! -f "$PROJECT/onscripter-ru.exe" ] && [ ! -f "$PROJECT/onscripter.exe" ]; then echo "ERROR: El proyecto ONScripter no tiene archivos reconocibles (0.txt, nscript.dat, ons.cfg, onscripter*.exe)" exit 1 fi fi # ----------------------------------------------------------------------------- # Variables adicionales por motor # ----------------------------------------------------------------------------- ONSCRIPTER_WEB_DIR="/opt/novelas/tools/onscripter-yuri/onsyuri_web" # ----------------------------------------------------------------------------- # Preparar directorios # ----------------------------------------------------------------------------- DEST="$BUILDS_DIR/$SAFE_NAME" rm -rf "$DEST" mkdir -p "$DEST" # ----------------------------------------------------------------------------- # Compilar / copiar segun motor # ----------------------------------------------------------------------------- if [ "$ENGINE" = "renpy" ]; then echo "[+] Compilando '$SAFE_NAME' (Ren'Py) desde $PROJECT ..." cd "$RENPY_HOME" # El web_build de Ren'Py ocasionalmente se cuelga al finalizar; usamos timeout timeout 600 xvfb-run -a ./renpy.sh launcher web_build "$PROJECT" --destination "$DEST" || true if [ ! -f "$DEST/index.html" ]; then echo "ERROR: No se genero index.html. Revisa el log del proyecto." exit 1 fi elif [ "$ENGINE" = "onscripter" ]; then echo "[+] Empaquetando '$SAFE_NAME' (ONScripter) desde $PROJECT ..." cp -r "$PROJECT"/* "$DEST/" echo "[+] Copiando motor OnscripterYuri web ..." cp "$ONSCRIPTER_WEB_DIR/onsyuri.js" "$DEST/onsyuri.js" cp "$ONSCRIPTER_WEB_DIR/onsyuri.wasm" "$DEST/onsyuri.wasm" echo "[+] Generando onsyuri_index.json ..." python3 "$ONSCRIPTER_WEB_DIR/onsyuri_index.py" \ -i "$DEST" \ -o "$DEST/onsyuri_index.json" \ --title "$SAFE_NAME" \ --gamedir "/games/$SAFE_NAME" \ --savedir "/onsyuri_save/$SAFE_NAME" \ --urlbase "" \ --lazyload else echo "[+] Copiando '$SAFE_NAME' ($ENGINE) desde $PROJECT ..." cp -r "$PROJECT"/* "$DEST/" if [ ! -f "$DEST/index.html" ]; then echo "ERROR: No se encontro index.html en el proyecto." exit 1 fi fi # ----------------------------------------------------------------------------- # Leer metadatos (meta.json opcional) o usar defaults # ----------------------------------------------------------------------------- if [ -f "$META_FILE" ]; then echo "[*] Cargando metadatos desde $META_FILE" TITLE=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('title','$SAFE_NAME'))") SUBTITLE=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('subtitle',''))") DESCRIPTION=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('description',''))") AUTHOR=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('author',''))") VERSION=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('version','1.0'))") COVER=$(python3 -c "import json; print(json.load(open('$META_FILE')).get('cover',''))") else TITLE="$SAFE_NAME" SUBTITLE="" DESCRIPTION="" AUTHOR="" VERSION="1.0" COVER="" fi # Cover por defecto segun motor / disponibilidad if [ -z "$COVER" ]; then if [ "$ENGINE" = "renpy" ] && [ -f "$DEST/web-presplash.jpg" ]; then COVER="/games/$SAFE_NAME/web-presplash.jpg" elif [ -f "$DEST/cover.jpg" ]; then COVER="/games/$SAFE_NAME/cover.jpg" elif [ -f "$DEST/cover.png" ]; then COVER="/games/$SAFE_NAME/cover.png" else COVER="" fi fi # Generar index.html para ONScripter tras tener metadatos reales if [ "$ENGINE" = "onscripter" ]; then echo "[+] Adaptando onsyuri.html como index.html ..." sed -e "s|[^<]*|$(echo "$TITLE" | sed 's/[&/\\]/\\&/g')|" \ "$ONSCRIPTER_WEB_DIR/onsyuri.html" > "$DEST/index.html" if [ ! -f "$DEST/index.html" ]; then echo "ERROR: No se genero index.html para ONScripter." exit 1 fi fi # Determinar si requiere headers COOP/COEP (solo Ren'Py Web los necesita) if [ "$ENGINE" = "renpy" ]; then COOP_COEP="true" else COOP_COEP="false" fi CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # ----------------------------------------------------------------------------- # Generar game.json # ----------------------------------------------------------------------------- cat > "$DEST/game.json" < # ----------------------------------------------------------------------------- echo "[+] Publicando en $WWW_GAMES/$SAFE_NAME ..." rm -rf "$WWW_GAMES/$SAFE_NAME" mkdir -p "$WWW_GAMES/$SAFE_NAME" cp -r "$DEST"/* "$WWW_GAMES/$SAFE_NAME/" chown -R www-data:www-data "$WWW_GAMES/$SAFE_NAME" chmod -R 755 "$WWW_GAMES/$SAFE_NAME" # ----------------------------------------------------------------------------- # Generar snippet nginx para Ren'Py # ----------------------------------------------------------------------------- mkdir -p "$NGINX_SNIPPETS_DIR" SNIPPET_FILE="$NGINX_SNIPPETS_DIR/$SAFE_NAME.conf" if [ "$COOP_COEP" = "true" ]; then sed "s/__SLUG__/$SAFE_NAME/g" /opt/novelas/config/nginx-snippet.template > "$SNIPPET_FILE" echo "[+] Snippet nginx generado: $SNIPPET_FILE" else rm -f "$SNIPPET_FILE" echo "[*] No se requiere snippet COOP/COEP para $ENGINE" fi # ----------------------------------------------------------------------------- # Actualizar catalogo global /var/www/novelas/games.json # ----------------------------------------------------------------------------- echo "[+] Actualizando catalogo global ..." python3 </games/$SAFE_NAME/"