291 lines
10 KiB
Bash
Executable File
291 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# build-novela.sh - Compila y publica una novela visual en NovelasVM
|
|
# Uso: build-novela.sh <slug> <ruta_proyecto> [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 <slug> <ruta_proyecto> [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|<title>[^<]*</title>|<title>$(echo "$TITLE" | sed 's/[&/\\]/\\&/g')</title>|" \
|
|
"$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" <<EOF
|
|
{
|
|
"slug": "$SAFE_NAME",
|
|
"title": "$TITLE",
|
|
"subtitle": "$SUBTITLE",
|
|
"engine": "$ENGINE",
|
|
"description": "$DESCRIPTION",
|
|
"cover": "$COVER",
|
|
"version": "$VERSION",
|
|
"author": "$AUTHOR",
|
|
"createdAt": "$CREATED_AT",
|
|
"entryPoint": "/games/$SAFE_NAME/index.html",
|
|
"coopCoep": $COOP_COEP
|
|
}
|
|
EOF
|
|
|
|
echo "[+] game.json generado"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Publicar en /var/www/novelas/games/<slug>
|
|
# -----------------------------------------------------------------------------
|
|
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 <<PYEOF
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
|
|
catalog_path = "$WWW_ROOT/games.json"
|
|
games = {}
|
|
|
|
if os.path.exists(catalog_path):
|
|
try:
|
|
with open(catalog_path, "r", encoding="utf-8") as f:
|
|
games = json.load(f)
|
|
except Exception as e:
|
|
print(f"[!] Advertencia: no se pudo leer catalogo existente: {e}")
|
|
games = {}
|
|
|
|
if "games" not in games or not isinstance(games["games"], list):
|
|
games["games"] = []
|
|
|
|
# Eliminar entrada previa del mismo slug
|
|
games["games"] = [g for g in games["games"] if g.get("slug") != "$SAFE_NAME"]
|
|
|
|
# Cargar metadatos del juego recien publicado
|
|
with open("$WWW_GAMES/$SAFE_NAME/game.json", "r", encoding="utf-8") as f:
|
|
game_meta = json.load(f)
|
|
|
|
games["games"].append(game_meta)
|
|
games["games"].sort(key=lambda g: g.get("title", g.get("slug", "")).lower())
|
|
games["updatedAt"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
games["version"] = "1.0"
|
|
|
|
with open(catalog_path, "w", encoding="utf-8") as f:
|
|
json.dump(games, f, indent=2, ensure_ascii=False)
|
|
|
|
print(f"[+] Catalogo actualizado: {len(games['games'])} juego(s)")
|
|
PYEOF
|
|
|
|
chown www-data:www-data "$WWW_ROOT/games.json"
|
|
chmod 644 "$WWW_ROOT/games.json"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Recargar nginx
|
|
# -----------------------------------------------------------------------------
|
|
echo "[+] Recargando nginx ..."
|
|
nginx -t && nginx -s reload
|
|
|
|
echo "[+] Listo: http://<ip>/games/$SAFE_NAME/"
|