#!/usr/bin/env bash # Ejecuta el update real: lo dispara el backend (POST /api/updater/apply) via un proceso # separado y detached, nunca corre inline en el request HTTP (el propio backend se reinicia # al final). # # IMPORTANTE: este script se ejecuta desde una copia FUERA de source/ (normalmente # $APP_DIR/scripts/apply-update.sh) — nunca desde dentro de source/, porque el propio script # hace `git reset --hard` sobre source/ mientras corre. Si se ejecutara desde ahi, se # sobreescribiria a si mismo a mitad de ejecucion (comportamiento indefinido en bash). # # Variables requeridas en el entorno (via EnvironmentFile del backend, ya cargadas por el # proceso Node que invoca este script): # APP_DIR ej. /opt/apps/project.exilaser.net # BACKEND_SERVICE ej. project-exilaser-backend # FRONTEND_SERVICE ej. project-exilaser-frontend # DB_HOST / DB_USER / DB_PASSWORD / DB_NAME (ya vienen del .env del backend) set -euo pipefail : "${APP_DIR:?falta APP_DIR}" : "${BACKEND_SERVICE:?falta BACKEND_SERVICE}" : "${FRONTEND_SERVICE:?falta FRONTEND_SERVICE}" : "${TARGET_VERSION:?falta TARGET_VERSION}" LOG="$APP_DIR/logs/update.log" mkdir -p "$APP_DIR/logs" "$APP_DIR/backups" exec >>"$LOG" 2>&1 PHASE=backup write_status() { printf '%s|%s|%s\n' "$1" "$PHASE" "$TARGET_VERSION" > "$APP_DIR/logs/update.status.tmp" mv "$APP_DIR/logs/update.status.tmp" "$APP_DIR/logs/update.status" } trap 'write_status failed' ERR write_status running echo "" echo "=== update start $(date -Iseconds) ===" echo "-- backup de base de datos --" mysqldump --single-transaction -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" \ > "$APP_DIR/backups/pre_update_$(date +%Y%m%d_%H%M%S).sql" echo "-- git fetch + reset --" PHASE=source write_status running cd "$APP_DIR/source" git fetch origin main git reset --hard origin/main NEW_VERSION="$(cat VERSION)" echo "version objetivo: $NEW_VERSION" [[ "$NEW_VERSION" == "$TARGET_VERSION" ]] || { echo "version remota distinta de la solicitada: $TARGET_VERSION"; false; } echo "-- backend: npm install + migraciones --" PHASE=backend write_status running cd "$APP_DIR/source/backend" npm install --omit=dev node src/scripts/migrate-db.js echo "-- frontend: npm install + build --" PHASE=frontend write_status running cd "$APP_DIR/source/frontend" # --include=dev es necesario a la fuerza: este script hereda NODE_ENV=production del backend # (via EnvironmentFile), y con eso npm salta devDependencies por defecto -- incluido vite, que # es la unica forma de hacer el build. Sin esto, "npm install" queda "up to date" sin instalar # nada realmente y el build falla con "vite: not found". npm install --include=dev npm run build echo "-- publicando build de frontend --" PHASE=publish write_status running rm -rf "$APP_DIR/frontend/dist" cp -r dist "$APP_DIR/frontend/dist" echo "-- reiniciando servicios --" PHASE=restart write_status running # Dos llamadas separadas a proposito: el sudoers NOPASSWD (ver DEPLOY_WEB.md) whitelistea cada # "systemctl restart " por separado -- un solo "systemctl restart back front" junto no # matchea ninguna de las dos reglas, sudo pide contraseña, falla en este contexto sin terminal, y # el restart nunca pasa (el resto del update ya corrio bien, pero los servicios quedan con el # codigo viejo sin que nada avise del error). Bug real encontrado el 2026-09-30. sudo systemctl restart "$BACKEND_SERVICE" sudo systemctl restart "$FRONTEND_SERVICE" PHASE=complete write_status done echo "=== update done $(date -Iseconds) -- v$NEW_VERSION ==="