Files
dtic-DIIAA/servicios/bitacoras/app-run.sh
T

778 lines
26 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# DTIC Bitácoras - Script Optimizado de Gestión de Aplicación
# =============================================================================
#
# Script optimizado para gestión completa de la aplicación DTIC Bitácoras
# Soporta modos interactivo y no interactivo con parámetros
#
# Características:
# - Modo interactivo (sin parámetros) con menús inteligentes
# - Modo no interactivo con parámetros: start, stop, restart, status
# - Verificación robusta de dependencias (Docker, docker-compose)
# - Validación completa de PostgreSQL y servicios
# - Cleanup automático de recursos huérfanos
# - Timeouts configurables vía variables de entorno
#
# Uso:
# ./app-run.sh # Modo interactivo
# ./app-run.sh start # Iniciar aplicación (no interactivo)
# ./app-run.sh stop # Detener aplicación (no interactivo)
# ./app-run.sh restart # Reiniciar aplicación (no interactivo)
# ./app-run.sh status # Mostrar estado detallado (no interactivo)
# ./app-run.sh bd-backup # Crear backup de base de datos (no interactivo)
# ./app-run.sh bd-restore # Restaurar base de datos desde backup (no interactivo)
#
# Variables de entorno:
# APP_TIMEOUT_CHECK=30 # Timeout para verificar servicios (segundos)
# APP_MAX_ATTEMPTS=20 # Máximo número de intentos de verificación
# APP_BACKUP_DIR="backups" # Directorio para backups
#
# Versión: 2.1 - Con funcionalidad de backup
# Fecha: 2025-11-06
# =============================================================================
set -e
# Change to script directory to allow running from anywhere
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Cargar biblioteca compartida
source "$SCRIPT_DIR/app-run_lib.sh"
# Configuración
API_PORT=3001
FRONTEND_PORT=5173
DB_PORT=5432
API_URL="http://localhost:$API_PORT"
FRONTEND_URL="http://localhost:$FRONTEND_PORT"
DB_URL="localhost:$DB_PORT"
# Configuración de timeouts (configurables vía entorno)
TIMEOUT_CHECK=${APP_TIMEOUT_CHECK:-30}
MAX_ATTEMPTS=${APP_MAX_ATTEMPTS:-20}
# Configuración de backup
BACKUP_DIR=${APP_BACKUP_DIR:-"backups"}
BACKUP_FORMAT="dtic_bitacoras_backup_%Y%m%d_%H%M%S.sql"
# Modo de operación
INTERACTIVE_MODE=true
COMMAND=""
# Función para verificar si la aplicación está ejecutándose
check_app_running() {
log "🔍 Verificando estado de contenedores Docker..."
# Verificar contenedores Docker
if docker compose ps 2>/dev/null | grep -q "dtic_bitacoras"; then
log "✅ Contenedores encontrados ejecutándose"
return 0
else
log "❌ No se encontraron contenedores ejecutándose"
return 1
fi
}
# Función para verificar conectividad a PostgreSQL
check_db_connection() {
log "🔍 Verificando conexión a PostgreSQL..."
# Método 1: Usar psql si está disponible
if command -v psql &> /dev/null; then
log "📡 Intentando conectar a PostgreSQL con psql..."
if PGPASSWORD=dtic_password psql -h localhost -p $DB_PORT -U dtic_user -d dtic_bitacoras -c "SELECT 1;" &> /dev/null; then
log "✅ Conexión a PostgreSQL exitosa (psql)"
return 0
else
log "❌ Fallo en conexión a PostgreSQL (psql)"
fi
else
log "⚠️ psql no disponible, intentando método alternativo con curl..."
fi
# Método 2: Verificar conectividad usando la API (que internamente usa PostgreSQL)
log "🌐 Verificando conectividad de BD a través de la API..."
if curl -s --max-time 5 "$API_URL/health" >/dev/null 2>&1; then
log "✅ API responde, indicando que PostgreSQL está conectado"
return 0
else
log "❌ API no responde, posible problema con PostgreSQL"
return 1
fi
}
# Función para verificar si la API está accesible
check_api_accessible() {
log "🔍 Verificando accesibilidad de la API..."
log "🌐 Probando endpoint: $API_URL/health"
if curl -s --max-time 5 "$API_URL/health" >/dev/null 2>&1; then
log "✅ API accesible"
return 0
else
log "❌ API no accesible"
return 1
fi
}
# Función para verificar si el frontend está accesible
check_frontend_accessible() {
log "🔍 Verificando accesibilidad del frontend..."
log "🌐 Probando URL: $FRONTEND_URL"
if curl -s --max-time 5 "$FRONTEND_URL" >/dev/null 2>&1; then
log "✅ Frontend accesible"
return 0
else
log "❌ Frontend no accesible"
return 1
fi
}
# Función para mostrar estado actual
show_status() {
echo ""
echo "📊 Estado Actual de DTIC Bitácoras:"
echo "=================================="
if check_app_running; then
echo -e "🐳 Contenedores: ${GREEN}✅ Ejecutándose${NC}"
if check_api_accessible; then
echo -e "🔧 API: ${GREEN}✅ Accesible${NC} (puerto $API_PORT)"
else
echo -e "🔧 API: ${RED}❌ No accesible${NC} (puerto $API_PORT)"
fi
if check_frontend_accessible; then
echo -e "🌐 Frontend: ${GREEN}✅ Accesible${NC} (puerto $FRONTEND_PORT)"
else
echo -e "🌐 Frontend: ${RED}❌ No accesible${NC} (puerto $FRONTEND_PORT)"
fi
if check_db_connection; then
echo -e "🗄️ Base de Datos: ${GREEN}✅ Conectada${NC} (puerto $DB_PORT)"
else
echo -e "🗄️ Base de Datos: ${RED}❌ No conectada${NC} (puerto $DB_PORT)"
fi
else
echo -e " Contenedores: ${RED}❌ Detenidos${NC}"
echo -e "🔧 API: ${YELLOW}❓ No verificable${NC}"
echo -e "🌐 Frontend: ${YELLOW}❓ No verificable${NC}"
echo -e "🗄️ Base de Datos: ${YELLOW}❓ No verificable${NC}"
fi
echo ""
}
# Función para mostrar menú de opciones
show_menu() {
echo "Opciones disponibles:"
echo "==================="
local option_num=1
if check_app_running; then
echo -e "${GREEN}${option_num})${NC} 🛑 Detener aplicación"
((option_num++))
echo -e "${GREEN}${option_num})${NC} 🔄 Reiniciar aplicación"
((option_num++))
echo -e "${YELLOW}${option_num})${NC} 💾 Crear backup de BD"
((option_num++))
echo -e "${YELLOW}${option_num})${NC} 🔄 Restaurar BD desde backup"
((option_num++))
else
echo -e "${GREEN}${option_num})${NC} ▶️ Iniciar aplicación"
((option_num++))
echo -e "${YELLOW}${option_num})${NC} 💾 Crear backup de BD"
((option_num++))
echo -e "${YELLOW}${option_num})${NC} 🔄 Restaurar BD desde backup"
((option_num++))
fi
echo -e "${BLUE}${option_num})${NC} 📊 Ver estado detallado"
((option_num++))
echo -e "${BLUE}${option_num})${NC} 🚪 Salir"
echo ""
}
# Función para iniciar aplicación
start_app() {
log "🚀 Iniciando aplicación DTIC Bitácoras..."
if check_app_running; then
warning "La aplicación ya está ejecutándose"
return 0
fi
# Cleanup automático antes de iniciar
cleanup_resources
log "🐳 Ejecutando 'docker compose up --build -d'..."
if docker compose up --build -d; then
success "Aplicación iniciada"
# Esperar a que esté lista
log "⏳ Esperando que los servicios estén listos..."
sleep 5
local attempts=0
while [ $attempts -lt $MAX_ATTEMPTS ]; do
log "🔄 Intento $((attempts+1))/$MAX_ATTEMPTS de verificación de servicios..."
if check_api_accessible && check_frontend_accessible && check_db_connection; then
success "✅ Todos los servicios están listos"
if [ "$INTERACTIVE_MODE" = false ]; then
show_access_urls
fi
break
fi
sleep 3
((attempts++))
done
if [ $attempts -eq $MAX_ATTEMPTS ]; then
warning "⚠️ Algunos servicios pueden no estar completamente listos aún"
log "📊 Estado final de servicios:"
check_api_accessible
check_frontend_accessible
check_db_connection
fi
else
error "❌ Fallo al iniciar la aplicación"
log "🔍 Verificando logs de Docker para más detalles..."
docker compose logs --tail=20
return 1
fi
}
# Función para detener aplicación
stop_app() {
log "🛑 Deteniendo aplicación DTIC Bitácoras..."
if ! check_app_running; then
warning "La aplicación no está ejecutándose"
return 0
fi
log "🐳 Ejecutando 'docker compose down'..."
if docker compose down; then
success "Aplicación detenida"
# Cleanup automático después de detener
cleanup_resources
else
error "❌ Fallo al detener la aplicación"
log "🔍 Verificando estado actual de contenedores..."
docker compose ps
return 1
fi
}
# Función para cleanup de recursos
cleanup_resources() {
log "🧹 Realizando limpieza automática de recursos..."
# Limpiar contenedores detenidos
log "🗑️ Limpiando contenedores detenidos..."
if docker container prune -f >/dev/null 2>&1; then
log "✅ Contenedores huérfanos limpiados"
else
log "⚠️ No se pudieron limpiar contenedores huérfanos"
fi
# Limpiar imágenes no utilizadas (opcional, solo si hay muchas)
# docker image prune -f >/dev/null 2>&1
# Limpiar volúmenes huérfanos (con cuidado)
log "🗂️ Limpiando volúmenes huérfanos..."
if docker volume prune -f >/dev/null 2>&1; then
log "✅ Volúmenes huérfanos limpiados"
else
log "⚠️ No se pudieron limpiar volúmenes huérfanos"
fi
}
# Función para crear backup de la base de datos
backup_database() {
log "💾 Iniciando proceso de backup de base de datos..."
# Verificar que la aplicación esté ejecutándose
if ! check_app_running; then
error "❌ La aplicación no está ejecutándose. Inicia la aplicación antes de hacer backup."
return 1
fi
# Crear directorio de backups si no existe
log "📁 Verificando directorio de backups: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
error "❌ No se pudo crear el directorio de backups: $BACKUP_DIR"
return 1
fi
# Generar nombre del archivo de backup con timestamp
local backup_filename
backup_filename=$(date +"$BACKUP_FORMAT")
local backup_path="$BACKUP_DIR/$backup_filename"
log "📄 Archivo de backup: $backup_path"
# Verificar conexión a PostgreSQL
if ! check_db_connection; then
error "❌ No se puede conectar a la base de datos. Verifica que PostgreSQL esté funcionando."
return 1
fi
# Obtener credenciales de la base de datos desde docker-compose
local db_host="localhost"
local db_port=$DB_PORT
local db_name="dtic_bitacoras"
local db_user="dtic_user"
local db_password="dtic_password"
# Verificar si hay variables de entorno en el archivo .env
if [ -f "backend/.env" ]; then
source backend/.env 2>/dev/null || true
fi
log "🔐 Conectando a PostgreSQL para crear backup..."
log "📡 Host: $db_host:$db_port"
log "🗄️ Base de datos: $db_name"
log "👤 Usuario: $db_user"
# Verificar si psql está disponible localmente
local use_docker_psql=false
if ! command -v psql &> /dev/null; then
log "⚠️ psql no está disponible localmente, usando Docker container..."
use_docker_psql=true
fi
# Crear el backup usando pg_dump
log "⏳ Ejecutando pg_dump..."
local backup_success=false
if [ "$use_docker_psql" = true ]; then
# Usar psql desde el contenedor de PostgreSQL
log "🐳 Usando PostgreSQL client desde Docker container..."
# Crear el backup dentro del contenedor y copiarlo al host
local temp_backup_name="temp_backup_$(date +%s).sql"
if docker exec -e PGPASSWORD="$db_password" dtic_bitacoras_postgres pg_dump -h localhost -p 5432 -U "$db_user" -d "$db_name" -f "/tmp/$temp_backup_name"; then
log "📁 Copiando backup desde contenedor al host..."
if docker cp dtic_bitacoras_postgres:/tmp/"$temp_backup_name" "$backup_path"; then
# Limpiar el archivo temporal del contenedor
docker exec dtic_bitacoras_postgres rm -f "/tmp/$temp_backup_name"
backup_success=true
else
error "❌ Error al copiar el backup desde el contenedor"
fi
else
error "❌ Error al crear el backup dentro del contenedor"
fi
else
# Usar psql local
log "💻 Usando PostgreSQL client local..."
if PGPASSWORD="$db_password" pg_dump -h "$db_host" -p "$db_port" -U "$db_user" -d "$db_name" -f "$backup_path"; then
backup_success=true
fi
fi
if [ "$backup_success" = true ]; then
success "✅ Backup creado exitosamente: $backup_path"
# Verificar que el archivo se creó y tiene contenido
if [ -f "$backup_path" ] && [ -s "$backup_path" ]; then
local file_size
file_size=$(du -h "$backup_path" | cut -f1)
log "📊 Tamaño del backup: $file_size"
# Mostrar información adicional del backup
log "🔍 Verificando integridad del backup..."
local line_count
line_count=$(wc -l < "$backup_path")
log "📋 Líneas en el backup: $line_count"
if [ $line_count -gt 10 ]; then
success "✅ Backup verificado correctamente"
else
warning "⚠️ El backup parece tener pocas líneas, pero se creó exitosamente"
fi
return 0
else
error "❌ El archivo de backup se creó pero está vacío o no existe"
return 1
fi
else
error "❌ Error al crear el backup. Verifica la conectividad y credenciales de la base de datos."
# Limpiar archivo de backup parcial si existe
if [ -f "$backup_path" ]; then
rm -f "$backup_path"
log "🗑️ Archivo de backup parcial eliminado"
fi
return 1
fi
}
# Función para restaurar base de datos desde backup
restore_database() {
log "💾 Iniciando proceso de restauración de base de datos..."
# Verificar que la aplicación esté ejecutándose
if ! check_app_running; then
error "❌ La aplicación no está ejecutándose. Inicia la aplicación antes de restaurar."
return 1
fi
# Ruta del archivo de backup
local backup_file="backups/dtic_bitacoras_backup_20251107_171026.sql"
# Verificar que el archivo existe
if [ ! -f "$backup_file" ]; then
error "❌ Archivo de backup no encontrado: $backup_file"
return 1
fi
# Verificar conexión a PostgreSQL
if ! check_db_connection; then
error "❌ No se puede conectar a la base de datos. Verifica que PostgreSQL esté funcionando."
return 1
fi
# Obtener credenciales de la base de datos
local db_name="dtic_bitacoras"
local db_user="dtic_user"
local db_password="dtic_password"
log "🔐 Conectando a PostgreSQL para restaurar..."
log "📄 Archivo de backup: $backup_file"
log "🗄️ Base de datos: $db_name"
log "👤 Usuario: $db_user"
# Copiar archivo de backup al contenedor
local temp_backup_name="restore_backup.sql"
log "📁 Copiando archivo de backup al contenedor..."
if ! docker cp "$backup_file" dtic_bitacoras_postgres:/tmp/"$temp_backup_name"; then
error "❌ Error al copiar el archivo de backup al contenedor"
return 1
fi
# Preparar base de datos para restauración (drop y recreate)
log "🗄️ Preparando base de datos para restauración..."
if ! docker exec -e PGPASSWORD="$db_password" dtic_bitacoras_postgres psql -h localhost -p 5432 -U "$db_user" -d postgres -c "DROP DATABASE IF EXISTS $db_name;"; then
error "❌ Error al eliminar la base de datos existente"
# Limpiar archivo temporal
docker exec dtic_bitacoras_postgres rm -f "/tmp/$temp_backup_name" 2>/dev/null || true
return 1
fi
if ! docker exec -e PGPASSWORD="$db_password" dtic_bitacoras_postgres psql -h localhost -p 5432 -U "$db_user" -d postgres -c "CREATE DATABASE $db_name;"; then
error "❌ Error al crear la base de datos"
# Limpiar archivo temporal
docker exec dtic_bitacoras_postgres rm -f "/tmp/$temp_backup_name" 2>/dev/null || true
return 1
fi
# Ejecutar restauración
log "⏳ Ejecutando restauración desde backup..."
if docker exec -e PGPASSWORD="$db_password" dtic_bitacoras_postgres psql -h localhost -p 5432 -U "$db_user" -d "$db_name" -f "/tmp/$temp_backup_name"; then
success "✅ Restauración completada exitosamente"
# Verificar que la restauración fue exitosa
log "🔍 Verificando restauración..."
if docker exec -e PGPASSWORD="$db_password" dtic_bitacoras_postgres psql -h localhost -p 5432 -U "$db_user" -d "$db_name" -c "SELECT 1;" &>/dev/null; then
success "✅ Base de datos restaurada y verificada correctamente"
else
warning "⚠️ La restauración se completó pero la verificación falló"
fi
# Limpiar archivo temporal
docker exec dtic_bitacoras_postgres rm -f "/tmp/$temp_backup_name"
return 0
else
error "❌ Error durante la restauración"
# Limpiar archivo temporal
docker exec dtic_bitacoras_postgres rm -f "/tmp/$temp_backup_name"
return 1
fi
}
# Función para reiniciar aplicación
restart_app() {
log "🔄 Reiniciando aplicación DTIC Bitácoras..."
if ! check_app_running; then
warning "La aplicación no está ejecutándose. Iniciándola..."
start_app
return $?
fi
log "🐳 Ejecutando 'docker compose restart'..."
if docker compose restart; then
success "Aplicación reiniciada"
# Verificar que esté funcionando después del reinicio
log "⏳ Verificando servicios después del reinicio..."
sleep 3
if check_api_accessible && check_frontend_accessible; then
success "✅ Servicios verificados después del reinicio"
else
warning "⚠️ Algunos servicios pueden tardar en estar listos"
log "📊 Estado de servicios post-reinicio:"
check_api_accessible
check_frontend_accessible
fi
else
error "❌ Fallo al reiniciar la aplicación"
log "🔍 Verificando logs de reinicio..."
docker compose logs --tail=10
return 1
fi
}
# Función para mostrar estado detallado
show_detailed_status() {
echo ""
echo "📊 Estado Detallado del Sistema:"
echo "==============================="
echo "🐳 Contenedores Docker:"
docker compose ps
echo ""
if check_app_running; then
echo "🔧 API Health Check:"
if check_api_accessible; then
curl -s "$API_URL/health" | jq . 2>/dev/null || curl -s "$API_URL/health"
else
echo -e "${RED}❌ API no accesible${NC}"
fi
echo ""
echo "🌐 Frontend Status:"
if check_frontend_accessible; then
echo -e "${GREEN}✅ Frontend accesible${NC}"
else
echo -e "${RED}❌ Frontend no accesible${NC}"
fi
echo ""
echo "🗄️ Base de Datos:"
if check_db_connection; then
echo -e "${GREEN}✅ Conexión exitosa a PostgreSQL${NC}"
# Mostrar información básica de la BD
if command -v psql &> /dev/null; then
echo "Tablas en la base de datos:"
PGPASSWORD=dtic_password psql -h localhost -p $DB_PORT -U dtic_user -d dtic_bitacoras -c "\dt" 2>/dev/null | head -10
fi
else
echo -e "${RED}❌ No se puede conectar a PostgreSQL${NC}"
fi
echo ""
fi
}
# Función para mostrar URLs de acceso
show_access_urls() {
echo ""
echo "🌐 URLs de Acceso:"
echo "================="
if check_api_accessible; then
echo -e "🔧 API: ${GREEN}$API_URL${NC}"
echo -e " Health Check: ${GREEN}$API_URL/health${NC}"
else
echo -e "🔧 API: ${RED}$API_URL${NC} (no accesible)"
fi
if check_frontend_accessible; then
echo -e "🌐 Frontend: ${GREEN}$FRONTEND_URL${NC}"
else
echo -e "🌐 Frontend: ${RED}$FRONTEND_URL${NC} (no accesible)"
fi
echo ""
echo -e "${BLUE}💡 Para acceder a la aplicación, use la URL del Frontend en su navegador${NC}"
}
# Función para procesar parámetros de línea de comandos
parse_command() {
case "$1" in
start)
INTERACTIVE_MODE=false
COMMAND="start"
;;
stop)
INTERACTIVE_MODE=false
COMMAND="stop"
;;
restart)
INTERACTIVE_MODE=false
COMMAND="restart"
;;
status)
INTERACTIVE_MODE=false
COMMAND="status"
;;
bd-backup)
INTERACTIVE_MODE=false
COMMAND="backup"
;;
bd-restore)
INTERACTIVE_MODE=false
COMMAND="restore"
;;
*)
INTERACTIVE_MODE=true
;;
esac
}
# Función principal
main() {
# Ejecutar configuración inicial solo en la primera ejecución
if is_first_run; then
"$SCRIPT_DIR/app-run_deps.sh" initial-setup || exit 1
fi
# Procesar parámetros de línea de comandos
parse_command "$1"
if [ "$INTERACTIVE_MODE" = false ]; then
# Modo no interactivo
case "$COMMAND" in
start)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
start_app
;;
stop)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
stop_app
;;
restart)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
restart_app
;;
status)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
show_status
show_access_urls
;;
backup)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
backup_database
;;
restore)
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
restore_database
;;
esac
else
# Modo interactivo (comportamiento original)
echo ""
echo "🚀 DTIC Bitácoras - Gestor Inteligente"
echo "====================================="
echo ""
# Mostrar estado inicial
show_status
# Bucle principal
while true; do
show_menu
# Determinar rango de opciones válido
local max_option
if check_app_running; then
max_option=6 # 1:detener, 2:reiniciar, 3:backup, 4:restore, 5:estado, 6:salir
read -p "Seleccione una opción (1-6): " choice
else
max_option=5 # 1:iniciar, 2:backup, 3:restore, 4:estado, 5:salir
read -p "Seleccione una opción (1-5): " choice
fi
case $choice in
1)
if check_app_running; then
stop_app
else
start_app
fi
echo ""
read -p "Presione Enter para continuar..."
;;
2)
if check_app_running; then
restart_app
else
backup_database
fi
echo ""
read -p "Presione Enter para continuar..."
;;
3)
if check_app_running; then
backup_database
else
restore_database
fi
echo ""
read -p "Presione Enter para continuar..."
;;
4)
if check_app_running; then
restore_database
else
show_detailed_status
fi
echo ""
read -p "Presione Enter para continuar..."
;;
5)
if check_app_running; then
show_detailed_status
else
echo ""
log "¡Hasta luego!"
break
fi
echo ""
read -p "Presione Enter para continuar..."
;;
6)
if check_app_running; then
echo ""
log "¡Hasta luego!"
break
else
error "Opción inválida."
sleep 2
fi
;;
*)
error "Opción inválida."
sleep 2
;;
esac
# Mostrar estado actualizado y URLs después de cada acción
show_status
show_access_urls
done
fi
}
# Verificar dependencias antes de continuar
"$SCRIPT_DIR/app-run_deps.sh" check-docker-deps || exit 1
# Verificar que estamos en el directorio correcto
if [ ! -f "docker-compose.yml" ]; then
error "No se encuentra docker-compose.yml. Asegúrese de ejecutar este script desde el directorio _app-bitacoras"
exit 1
fi
# Ejecutar función principal con parámetros
main "$@"