#!/bin/bash # sync_audio_bulk.sh — Sincroniza a producción, uno por uno, un lote de posts con # audio TTS ya generado en local. Envoltorio de sync_audio_to_prod.py que añade # "parar en el primer fallo" (el script Python no lo hace: recorre toda la # lista y acumula errores, ver #212/#213). # # Motivo del wrapper en bash con mapfile: un `while read id; do ...; done < # fichero` normal comparte el file descriptor de stdin con cualquier `ssh` # lanzado dentro del bucle. sync_audio_to_prod.py llama a ssh internamente; si # el bucle no está protegido, ssh se come el resto del fichero de IDs y el # bucle termina "sin error" tras procesar solo el primero. Detectado y # documentado en memoria (feedback-bash-while-read-ssh-stdin) el 2026-08-24 # tras un lote real que solo sincronizó 1 de 184 posts sin ningún error visible. # # Uso: # scripts/sync_audio_bulk.sh --ids 55519,55520,822 # scripts/sync_audio_bulk.sh --file /tmp/ids.txt # un ID por línea # scripts/sync_audio_bulk.sh --carta 54254 # cola completa de la carta # scripts/sync_audio_bulk.sh --ids 55519,55520 --dry-run # solo plan, no toca prod # # Requiere las mismas variables de entorno que sync_audio_to_prod.py para # apuntar a producción (ver ese script): FEA_PROD_SSH_HOST, # FEA_PROD_DOCKER_CONTAINER. Ejemplo: # export FEA_PROD_SSH_HOST="rafa@188.40.120.157" # export FEA_PROD_DOCKER_CONTAINER="wordpress-r2ssjifwj0r0ghyoqd528uaa" set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SYNC_PY="$SCRIPT_DIR/sync_audio_to_prod.py" usage() { echo "Uso: $0 (--ids id1,id2,... | --file ruta | --carta post_id) [--dry-run]" >&2 exit 2 } ids_csv="" ids_file="" carta="" dry_run="" while [ $# -gt 0 ]; do case "$1" in --ids) ids_csv="$2"; shift 2 ;; --file) ids_file="$2"; shift 2 ;; --carta) carta="$2"; shift 2 ;; --dry-run) dry_run="--dry-run"; shift ;; *) usage ;; esac done # Carga explícita de IDs en un array ANTES de cualquier llamada a ssh — es la # parte que evita el bug de stdin. No usar `while read ... < fichero`. ids=() if [ -n "$ids_csv" ]; then IFS=',' read -r -a ids <<< "$ids_csv" elif [ -n "$ids_file" ]; then mapfile -t ids < "$ids_file" elif [ -n "$carta" ]; then mapfile -t ids < <( python3 - "$carta" <<'PYEOF' import subprocess, sys sys.path.insert(0, "scripts") from sync_audio_to_prod import carta_article_ids for pid in carta_article_ids(int(sys.argv[1])): print(pid) PYEOF ) else usage fi total=${#ids[@]} if [ "$total" -eq 0 ]; then echo "Nada que sincronizar (lista de IDs vacía)." >&2 exit 0 fi ok=0 skip=0 n=0 for id in "${ids[@]}"; do id="$(echo "$id" | tr -d '[:space:]')" [ -z "$id" ] && continue n=$((n + 1)) # stdin del hijo explícitamente en /dev/null: cinturón y tirantes además del # array en memoria, por si algún día se cambia el bucle. out="$(python3 "$SYNC_PY" --ids "$id" $dry_run < /dev/null)" rc=$? echo "$out" if [ $rc -ne 0 ]; then echo "=== ABORT: fallo en #$id ($n/$total procesados, $ok ok). Deteniendo lote, sin reintentar. ===" exit 1 fi if echo "$out" | grep -q "PLAN: subiría\|#$id: ok"; then ok=$((ok + 1)) else skip=$((skip + 1)) fi done echo "=== FIN LOTE. ok=$ok skip=$skip de $total candidatos. ==="