FlotillasGPS - Sistema completo de monitoreo de flotillas GPS

Sistema completo para monitoreo y gestion de flotas de vehiculos con:
- Backend FastAPI con PostgreSQL/TimescaleDB
- Frontend React con TypeScript y TailwindCSS
- App movil React Native con Expo
- Soporte para dispositivos GPS, Meshtastic y celulares
- Video streaming en vivo con MediaMTX
- Geocercas, alertas, viajes y reportes
- Autenticacion JWT y WebSockets en tiempo real

Documentacion completa y guias de usuario incluidas.
This commit is contained in:
FlotillasGPS Developer
2026-01-21 08:18:00 +00:00
commit 51d78bacf4
248 changed files with 50171 additions and 0 deletions

486
deploy/scripts/backup.sh Normal file
View File

@@ -0,0 +1,486 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Script de Backup
# ============================================
# Realiza backup de base de datos y configuracion
#
# Uso: ./backup.sh [--full] [--upload] [--keep-days N]
#
# Opciones:
# --full Incluir backup completo de archivos
# --upload Subir a S3/remote despues del backup
# --keep-days Dias de retencion (default: 7)
# ============================================
set -e
set -o pipefail
# ---------------------------------------------
# Colores
# ---------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ---------------------------------------------
# Variables de Configuracion
# ---------------------------------------------
INSTALL_DIR="${INSTALL_DIR:-/opt/flotillas}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/flotillas}"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
# Cargar variables de entorno
if [[ -f "$INSTALL_DIR/.env" ]]; then
export $(grep -v '^#' "$INSTALL_DIR/.env" | xargs)
fi
# Base de datos
DB_HOST="${POSTGRES_HOST:-localhost}"
DB_PORT="${POSTGRES_PORT:-5432}"
DB_NAME="${POSTGRES_DB:-flotillas}"
DB_USER="${POSTGRES_USER:-flotillas}"
DB_PASSWORD="${POSTGRES_PASSWORD:-}"
# S3 (opcional)
S3_ENABLED="${S3_ENABLED:-false}"
S3_BUCKET="${S3_BUCKET:-}"
S3_ENDPOINT="${S3_ENDPOINT:-https://s3.amazonaws.com}"
# Timestamp para este backup
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="flotillas_${TIMESTAMP}"
# Flags
FULL_BACKUP=false
UPLOAD_BACKUP=false
# ---------------------------------------------
# Funciones
# ---------------------------------------------
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
# Calcular tamanio de archivo
get_file_size() {
du -h "$1" 2>/dev/null | cut -f1
}
# Verificar espacio disponible
check_disk_space() {
local required_gb=$1
local free_space=$(df -BG "$BACKUP_DIR" | awk 'NR==2 {print $4}' | tr -d 'G')
if [[ $free_space -lt $required_gb ]]; then
log_error "Espacio insuficiente: ${free_space}GB disponible, se requieren ${required_gb}GB"
return 1
fi
return 0
}
# ---------------------------------------------
# Parsear argumentos
# ---------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--full)
FULL_BACKUP=true
shift
;;
--upload)
UPLOAD_BACKUP=true
shift
;;
--keep-days)
RETENTION_DAYS="$2"
shift 2
;;
--help|-h)
echo "Uso: $0 [--full] [--upload] [--keep-days N]"
echo ""
echo "Opciones:"
echo " --full Backup completo (DB + archivos)"
echo " --upload Subir a S3 despues del backup"
echo " --keep-days Dias de retencion (default: 7)"
exit 0
;;
*)
log_error "Opcion desconocida: $1"
exit 1
;;
esac
done
}
# ---------------------------------------------
# Crear directorio de backup
# ---------------------------------------------
prepare_backup_dir() {
log_info "Preparando directorio de backup..."
# Crear directorio si no existe
mkdir -p "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR/daily"
mkdir -p "$BACKUP_DIR/temp"
# Verificar permisos
if [[ ! -w "$BACKUP_DIR" ]]; then
log_error "No hay permisos de escritura en $BACKUP_DIR"
exit 1
fi
# Verificar espacio (minimo 5GB)
check_disk_space 5 || exit 1
log_success "Directorio listo: $BACKUP_DIR"
}
# ---------------------------------------------
# Backup de PostgreSQL
# ---------------------------------------------
backup_database() {
log_info "Iniciando backup de PostgreSQL..."
local db_backup_file="$BACKUP_DIR/temp/${BACKUP_NAME}_db.sql"
local db_backup_compressed="$BACKUP_DIR/daily/${BACKUP_NAME}_db.sql.gz"
# Configurar password para pg_dump
export PGPASSWORD="$DB_PASSWORD"
# Realizar dump
log_info "Exportando base de datos ${DB_NAME}..."
pg_dump \
-h "$DB_HOST" \
-p "$DB_PORT" \
-U "$DB_USER" \
-d "$DB_NAME" \
--format=plain \
--no-owner \
--no-acl \
--verbose \
-f "$db_backup_file" 2>/dev/null
# Comprimir
log_info "Comprimiendo backup..."
gzip -9 -c "$db_backup_file" > "$db_backup_compressed"
# Limpiar archivo temporal
rm -f "$db_backup_file"
# Limpiar variable de password
unset PGPASSWORD
local size=$(get_file_size "$db_backup_compressed")
log_success "Backup de BD completado: $db_backup_compressed ($size)"
# Backup de Traccar DB si existe
if [[ -n "${TRACCAR_DB_NAME:-}" ]]; then
log_info "Exportando base de datos Traccar..."
local traccar_backup="$BACKUP_DIR/daily/${BACKUP_NAME}_traccar.sql.gz"
export PGPASSWORD="$DB_PASSWORD"
pg_dump \
-h "$DB_HOST" \
-p "$DB_PORT" \
-U "$DB_USER" \
-d "${TRACCAR_DB_NAME}" \
--format=plain \
--no-owner \
--no-acl \
2>/dev/null | gzip -9 > "$traccar_backup"
unset PGPASSWORD
local traccar_size=$(get_file_size "$traccar_backup")
log_success "Backup de Traccar completado: $traccar_backup ($traccar_size)"
fi
}
# ---------------------------------------------
# Backup de configuracion
# ---------------------------------------------
backup_config() {
log_info "Respaldando archivos de configuracion..."
local config_backup="$BACKUP_DIR/daily/${BACKUP_NAME}_config.tar.gz"
# Lista de archivos a respaldar
local files_to_backup=(
"$INSTALL_DIR/.env"
"$INSTALL_DIR/deploy"
"/opt/traccar/conf/traccar.xml"
"/opt/mediamtx/mediamtx.yml"
"/etc/mosquitto/conf.d/flotillas.conf"
"/etc/systemd/system/flotillas-*.service"
"/etc/systemd/system/mediamtx.service"
)
# Crear archivo temporal con lista de archivos existentes
local file_list=$(mktemp)
for file in "${files_to_backup[@]}"; do
if [[ -e "$file" ]]; then
echo "$file" >> "$file_list"
fi
done
# Crear tarball
tar -czf "$config_backup" -T "$file_list" 2>/dev/null || true
# Limpiar
rm -f "$file_list"
local size=$(get_file_size "$config_backup")
log_success "Backup de configuracion completado: $config_backup ($size)"
}
# ---------------------------------------------
# Backup completo (archivos)
# ---------------------------------------------
backup_full() {
if [[ "$FULL_BACKUP" != "true" ]]; then
return
fi
log_info "Iniciando backup completo de archivos..."
local full_backup="$BACKUP_DIR/daily/${BACKUP_NAME}_full.tar.gz"
# Excluir directorios grandes innecesarios
tar -czf "$full_backup" \
--exclude="$INSTALL_DIR/backend/venv" \
--exclude="$INSTALL_DIR/frontend/node_modules" \
--exclude="$INSTALL_DIR/.git" \
--exclude="*.log" \
--exclude="*.pyc" \
--exclude="__pycache__" \
"$INSTALL_DIR" 2>/dev/null
local size=$(get_file_size "$full_backup")
log_success "Backup completo: $full_backup ($size)"
}
# ---------------------------------------------
# Rotar backups antiguos
# ---------------------------------------------
rotate_backups() {
log_info "Rotando backups antiguos (retencion: ${RETENTION_DAYS} dias)..."
local deleted=0
# Encontrar y eliminar backups mas antiguos que RETENTION_DAYS
while IFS= read -r -d '' file; do
rm -f "$file"
((deleted++))
done < <(find "$BACKUP_DIR/daily" -type f -name "flotillas_*.gz" -mtime +${RETENTION_DAYS} -print0 2>/dev/null)
if [[ $deleted -gt 0 ]]; then
log_info "Eliminados $deleted backups antiguos"
fi
# Mostrar espacio usado
local space_used=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1)
log_info "Espacio total usado por backups: $space_used"
}
# ---------------------------------------------
# Subir a S3
# ---------------------------------------------
upload_to_s3() {
if [[ "$UPLOAD_BACKUP" != "true" ]]; then
return
fi
if [[ "$S3_ENABLED" != "true" ]]; then
log_warn "S3 no esta habilitado. Configura S3_ENABLED=true en .env"
return
fi
if ! command -v aws &> /dev/null; then
log_warn "AWS CLI no instalado. Instalando..."
pip3 install awscli -q
fi
log_info "Subiendo backups a S3..."
# Configurar credenciales
export AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY}"
export AWS_SECRET_ACCESS_KEY="${S3_SECRET_KEY}"
# Subir archivos del dia
for file in "$BACKUP_DIR/daily/${BACKUP_NAME}"*.gz; do
if [[ -f "$file" ]]; then
local filename=$(basename "$file")
log_info "Subiendo: $filename"
aws s3 cp "$file" "s3://${S3_BUCKET}/backups/$(date +%Y/%m)/${filename}" \
--endpoint-url "$S3_ENDPOINT" \
--quiet
log_success "Subido: $filename"
fi
done
# Limpiar credenciales
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
log_success "Backup subido a S3"
}
# ---------------------------------------------
# Crear indice de backups
# ---------------------------------------------
create_backup_index() {
log_info "Actualizando indice de backups..."
local index_file="$BACKUP_DIR/backup_index.txt"
# Cabecera
cat > "$index_file" <<EOF
# Indice de Backups - Sistema de Flotillas
# Generado: $(date)
# Retencion: ${RETENTION_DAYS} dias
#
# Formato: fecha | tipo | archivo | tamanio
#
EOF
# Listar backups
for file in $(ls -t "$BACKUP_DIR/daily"/*.gz 2>/dev/null); do
local filename=$(basename "$file")
local size=$(get_file_size "$file")
local date=$(stat -c %y "$file" 2>/dev/null | cut -d' ' -f1)
local type="unknown"
case "$filename" in
*_db.sql.gz) type="database" ;;
*_config.tar.gz) type="config" ;;
*_full.tar.gz) type="full" ;;
*_traccar.sql.gz) type="traccar" ;;
esac
echo "$date | $type | $filename | $size" >> "$index_file"
done
log_success "Indice actualizado: $index_file"
}
# ---------------------------------------------
# Verificar integridad del backup
# ---------------------------------------------
verify_backup() {
log_info "Verificando integridad de backups..."
local errors=0
for file in "$BACKUP_DIR/daily/${BACKUP_NAME}"*.gz; do
if [[ -f "$file" ]]; then
if gzip -t "$file" 2>/dev/null; then
log_success "OK: $(basename "$file")"
else
log_error "CORRUPTO: $(basename "$file")"
((errors++))
fi
fi
done
if [[ $errors -gt 0 ]]; then
log_error "Se encontraron $errors archivos corruptos"
return 1
fi
return 0
}
# ---------------------------------------------
# Enviar notificacion
# ---------------------------------------------
send_notification() {
local status="$1"
local message="$2"
# Telegram (si esta configurado)
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" ]] && [[ -n "${TELEGRAM_CHAT_ID:-}" ]]; then
local emoji="✅"
[[ "$status" == "error" ]] && emoji="❌"
curl -s -X POST \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="${emoji} Backup Flotillas: ${message}" \
> /dev/null 2>&1
fi
}
# ---------------------------------------------
# Mostrar resumen
# ---------------------------------------------
show_summary() {
echo ""
log_success "=========================================="
log_success "BACKUP COMPLETADO"
log_success "=========================================="
echo ""
echo "Archivos creados:"
for file in "$BACKUP_DIR/daily/${BACKUP_NAME}"*.gz; do
if [[ -f "$file" ]]; then
echo " - $(basename "$file") ($(get_file_size "$file"))"
fi
done
echo ""
echo "Ubicacion: $BACKUP_DIR/daily/"
echo "Retencion: ${RETENTION_DAYS} dias"
echo ""
}
# ---------------------------------------------
# Main
# ---------------------------------------------
main() {
parse_args "$@"
log_info "=========================================="
log_info "INICIANDO BACKUP - $(date)"
log_info "=========================================="
# Ejecutar pasos
prepare_backup_dir
backup_database
backup_config
backup_full
rotate_backups
verify_backup || true
upload_to_s3
create_backup_index
show_summary
# Notificar exito
send_notification "success" "Backup completado exitosamente"
}
# Manejo de errores global
trap 'log_error "Backup fallido en linea $LINENO"; send_notification "error" "Backup fallido"; exit 1' ERR
# Ejecutar
main "$@"

View File

@@ -0,0 +1,247 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Health Check
# ============================================
# Verifica el estado de todos los servicios
#
# Uso: ./health-check.sh [--verbose] [--json]
# ============================================
set -o pipefail
# ---------------------------------------------
# Colores
# ---------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Variables
INSTALL_DIR="${INSTALL_DIR:-/opt/flotillas}"
VERBOSE=false
JSON_OUTPUT=false
EXIT_CODE=0
# Cargar variables de entorno
if [[ -f "$INSTALL_DIR/.env" ]]; then
export $(grep -v '^#' "$INSTALL_DIR/.env" | xargs)
fi
# ---------------------------------------------
# Funciones
# ---------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--verbose|-v) VERBOSE=true; shift ;;
--json|-j) JSON_OUTPUT=true; shift ;;
--help|-h)
echo "Uso: $0 [--verbose] [--json]"
exit 0
;;
*) shift ;;
esac
done
}
check_service() {
local service="$1"
local name="$2"
if systemctl is-active --quiet "$service" 2>/dev/null; then
echo "ok"
else
echo "fail"
fi
}
check_port() {
local port="$1"
if nc -z localhost "$port" 2>/dev/null; then
echo "ok"
else
echo "fail"
fi
}
check_url() {
local url="$1"
local timeout="${2:-5}"
if curl -sf --max-time "$timeout" "$url" > /dev/null 2>&1; then
echo "ok"
else
echo "fail"
fi
}
check_db() {
local host="${POSTGRES_HOST:-localhost}"
local port="${POSTGRES_PORT:-5432}"
local db="${POSTGRES_DB:-flotillas}"
local user="${POSTGRES_USER:-flotillas}"
if PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "$host" -p "$port" -U "$user" -d "$db" -c "SELECT 1" > /dev/null 2>&1; then
echo "ok"
else
echo "fail"
fi
}
check_redis() {
local password="${REDIS_PASSWORD:-}"
if [[ -n "$password" ]]; then
if redis-cli -a "$password" ping 2>/dev/null | grep -q "PONG"; then
echo "ok"
else
echo "fail"
fi
else
if redis-cli ping 2>/dev/null | grep -q "PONG"; then
echo "ok"
else
echo "fail"
fi
fi
}
print_status() {
local name="$1"
local status="$2"
local details="$3"
if [[ "$JSON_OUTPUT" == "true" ]]; then
return
fi
if [[ "$status" == "ok" ]]; then
echo -e " ${GREEN}[OK]${NC} $name"
else
echo -e " ${RED}[FAIL]${NC} $name"
EXIT_CODE=1
fi
if [[ "$VERBOSE" == "true" ]] && [[ -n "$details" ]]; then
echo -e " $details"
fi
}
# ---------------------------------------------
# Main
# ---------------------------------------------
main() {
parse_args "$@"
# Resultados para JSON
declare -A results
if [[ "$JSON_OUTPUT" != "true" ]]; then
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} HEALTH CHECK - Sistema de Flotillas${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${BLUE}Servicios Systemd:${NC}"
fi
# Servicios systemd
results[flotillas_api]=$(check_service "flotillas-api" "API Backend")
print_status "flotillas-api" "${results[flotillas_api]}"
results[flotillas_web]=$(check_service "flotillas-web" "Frontend Web")
print_status "flotillas-web" "${results[flotillas_web]}"
results[postgresql]=$(check_service "postgresql" "PostgreSQL")
print_status "postgresql" "${results[postgresql]}"
results[redis]=$(check_service "redis-server" "Redis")
print_status "redis" "${results[redis]}"
results[traccar]=$(check_service "traccar" "Traccar GPS")
print_status "traccar" "${results[traccar]}"
results[mediamtx]=$(check_service "mediamtx" "MediaMTX")
print_status "mediamtx" "${results[mediamtx]}"
results[mosquitto]=$(check_service "mosquitto" "Mosquitto MQTT")
print_status "mosquitto" "${results[mosquitto]}"
if [[ "$JSON_OUTPUT" != "true" ]]; then
echo ""
echo -e "${BLUE}Conectividad:${NC}"
fi
# Puertos
results[port_api]=$(check_port "${API_PORT:-8000}")
print_status "API (puerto ${API_PORT:-8000})" "${results[port_api]}"
results[port_frontend]=$(check_port "${FRONTEND_PORT:-3000}")
print_status "Frontend (puerto ${FRONTEND_PORT:-3000})" "${results[port_frontend]}"
results[port_traccar]=$(check_port "${TRACCAR_PORT:-5055}")
print_status "Traccar (puerto ${TRACCAR_PORT:-5055})" "${results[port_traccar]}"
results[port_rtsp]=$(check_port 8554)
print_status "MediaMTX RTSP (puerto 8554)" "${results[port_rtsp]}"
if [[ "$JSON_OUTPUT" != "true" ]]; then
echo ""
echo -e "${BLUE}Base de Datos:${NC}"
fi
# Base de datos
results[db_connection]=$(check_db)
print_status "PostgreSQL conexion" "${results[db_connection]}"
results[redis_connection]=$(check_redis)
print_status "Redis conexion" "${results[redis_connection]}"
if [[ "$JSON_OUTPUT" != "true" ]]; then
echo ""
echo -e "${BLUE}APIs:${NC}"
fi
# APIs
results[api_health]=$(check_url "http://localhost:${API_PORT:-8000}/health")
print_status "API /health" "${results[api_health]}"
results[mediamtx_api]=$(check_url "http://localhost:9997/v3/paths/list")
print_status "MediaMTX API" "${results[mediamtx_api]}"
# JSON output
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo "{"
echo " \"timestamp\": \"$(date -Iseconds)\","
echo " \"status\": \"$([ $EXIT_CODE -eq 0 ] && echo 'healthy' || echo 'unhealthy')\","
echo " \"checks\": {"
first=true
for key in "${!results[@]}"; do
if [[ "$first" != "true" ]]; then
echo ","
fi
first=false
printf " \"%s\": \"%s\"" "$key" "${results[$key]}"
done
echo ""
echo " }"
echo "}"
else
echo ""
if [[ $EXIT_CODE -eq 0 ]]; then
echo -e "${GREEN}Estado general: SALUDABLE${NC}"
else
echo -e "${RED}Estado general: PROBLEMAS DETECTADOS${NC}"
fi
echo ""
fi
exit $EXIT_CODE
}
main "$@"

1080
deploy/scripts/install.sh Normal file

File diff suppressed because it is too large Load Diff

133
deploy/scripts/logs.sh Normal file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Visor de Logs
# ============================================
# Muestra logs de los diferentes servicios
#
# Uso: ./logs.sh [servicio] [--follow] [--lines N]
#
# Servicios: api, web, traccar, mediamtx, postgres, redis, all
# ============================================
# Variables
LINES="${LINES:-100}"
FOLLOW=false
SERVICE="api"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ---------------------------------------------
# Parsear argumentos
# ---------------------------------------------
while [[ $# -gt 0 ]]; do
case $1 in
api|web|traccar|mediamtx|postgres|redis|mosquitto|all)
SERVICE="$1"
shift
;;
-f|--follow)
FOLLOW=true
shift
;;
-n|--lines)
LINES="$2"
shift 2
;;
--help|-h)
echo "Sistema de Flotillas - Visor de Logs"
echo ""
echo "Uso: $0 [servicio] [opciones]"
echo ""
echo "Servicios:"
echo " api - Backend FastAPI"
echo " web - Frontend"
echo " traccar - Traccar GPS"
echo " mediamtx - Video streaming"
echo " postgres - Base de datos"
echo " redis - Cache"
echo " mosquitto - MQTT"
echo " all - Todos los servicios"
echo ""
echo "Opciones:"
echo " -f, --follow Seguir logs en tiempo real"
echo " -n, --lines N Numero de lineas (default: 100)"
echo ""
echo "Ejemplos:"
echo " $0 api -f # Seguir logs de API"
echo " $0 traccar -n 500 # Ultimas 500 lineas de Traccar"
echo " $0 all -f # Todos los logs en tiempo real"
exit 0
;;
*)
echo "Opcion desconocida: $1"
exit 1
;;
esac
done
# ---------------------------------------------
# Mostrar logs
# ---------------------------------------------
show_logs() {
local unit="$1"
local name="$2"
echo -e "${BLUE}=== Logs de $name ===${NC}"
local cmd="journalctl -u $unit -n $LINES --no-pager"
if [[ "$FOLLOW" == "true" ]]; then
cmd="journalctl -u $unit -f"
fi
$cmd 2>/dev/null || echo "Servicio no disponible o sin logs"
echo ""
}
case $SERVICE in
api)
show_logs "flotillas-api" "API Backend"
;;
web)
show_logs "flotillas-web" "Frontend"
;;
traccar)
show_logs "traccar" "Traccar GPS"
# Tambien mostrar log de archivo si existe
if [[ -f "/opt/traccar/logs/tracker-server.log" ]]; then
echo -e "${BLUE}=== Log de archivo Traccar ===${NC}"
tail -n $LINES /opt/traccar/logs/tracker-server.log
fi
;;
mediamtx)
show_logs "mediamtx" "MediaMTX"
;;
postgres)
show_logs "postgresql" "PostgreSQL"
;;
redis)
show_logs "redis-server" "Redis"
;;
mosquitto)
show_logs "mosquitto" "Mosquitto MQTT"
;;
all)
if [[ "$FOLLOW" == "true" ]]; then
echo "Mostrando todos los logs en tiempo real..."
echo "Presiona Ctrl+C para salir"
echo ""
journalctl -u flotillas-api -u flotillas-web -u traccar -u mediamtx -u mosquitto -f
else
show_logs "flotillas-api" "API Backend"
show_logs "flotillas-web" "Frontend"
show_logs "traccar" "Traccar GPS"
show_logs "mediamtx" "MediaMTX"
show_logs "mosquitto" "Mosquitto MQTT"
fi
;;
esac

549
deploy/scripts/restore.sh Normal file
View File

@@ -0,0 +1,549 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Script de Restauracion
# ============================================
# Restaura backups de base de datos y configuracion
#
# Uso: ./restore.sh [--db backup.sql.gz] [--config config.tar.gz] [--list]
#
# Opciones:
# --db FILE Restaurar backup de base de datos
# --config FILE Restaurar backup de configuracion
# --list Listar backups disponibles
# --latest Restaurar el backup mas reciente
# --date YYYYMMDD Restaurar backup de fecha especifica
# ============================================
set -e
set -o pipefail
# ---------------------------------------------
# Colores
# ---------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ---------------------------------------------
# Variables
# ---------------------------------------------
INSTALL_DIR="${INSTALL_DIR:-/opt/flotillas}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/flotillas}"
# Cargar variables de entorno
if [[ -f "$INSTALL_DIR/.env" ]]; then
export $(grep -v '^#' "$INSTALL_DIR/.env" | xargs)
fi
# Base de datos
DB_HOST="${POSTGRES_HOST:-localhost}"
DB_PORT="${POSTGRES_PORT:-5432}"
DB_NAME="${POSTGRES_DB:-flotillas}"
DB_USER="${POSTGRES_USER:-flotillas}"
DB_PASSWORD="${POSTGRES_PASSWORD:-}"
# Opciones
DB_BACKUP=""
CONFIG_BACKUP=""
LIST_ONLY=false
USE_LATEST=false
RESTORE_DATE=""
# ---------------------------------------------
# Funciones
# ---------------------------------------------
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# ---------------------------------------------
# Parsear argumentos
# ---------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--db)
DB_BACKUP="$2"
shift 2
;;
--config)
CONFIG_BACKUP="$2"
shift 2
;;
--list)
LIST_ONLY=true
shift
;;
--latest)
USE_LATEST=true
shift
;;
--date)
RESTORE_DATE="$2"
shift 2
;;
--help|-h)
show_help
exit 0
;;
*)
log_error "Opcion desconocida: $1"
exit 1
;;
esac
done
}
show_help() {
echo "Sistema de Flotillas - Restauracion de Backup"
echo ""
echo "Uso: $0 [opciones]"
echo ""
echo "Opciones:"
echo " --db FILE Restaurar backup de base de datos"
echo " --config FILE Restaurar backup de configuracion"
echo " --list Listar backups disponibles"
echo " --latest Restaurar el backup mas reciente"
echo " --date YYYYMMDD Restaurar backup de fecha especifica"
echo ""
echo "Ejemplos:"
echo " $0 --list"
echo " $0 --latest"
echo " $0 --date 20240115"
echo " $0 --db /var/backups/flotillas/daily/flotillas_20240115_030000_db.sql.gz"
}
# ---------------------------------------------
# Listar backups disponibles
# ---------------------------------------------
list_backups() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} BACKUPS DISPONIBLES${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
if [[ ! -d "$BACKUP_DIR/daily" ]]; then
log_warn "No hay backups disponibles"
return
fi
echo -e "${GREEN}Base de Datos:${NC}"
echo "---------------------------------------------"
printf "%-40s %10s %s\n" "Archivo" "Tamanio" "Fecha"
echo "---------------------------------------------"
for file in $(ls -t "$BACKUP_DIR/daily"/*_db.sql.gz 2>/dev/null); do
local name=$(basename "$file")
local size=$(du -h "$file" | cut -f1)
local date=$(stat -c %y "$file" | cut -d' ' -f1)
printf "%-40s %10s %s\n" "$name" "$size" "$date"
done
echo ""
echo -e "${GREEN}Configuracion:${NC}"
echo "---------------------------------------------"
for file in $(ls -t "$BACKUP_DIR/daily"/*_config.tar.gz 2>/dev/null); do
local name=$(basename "$file")
local size=$(du -h "$file" | cut -f1)
local date=$(stat -c %y "$file" | cut -d' ' -f1)
printf "%-40s %10s %s\n" "$name" "$size" "$date"
done
echo ""
echo -e "${GREEN}Backups Completos:${NC}"
echo "---------------------------------------------"
for file in $(ls -t "$BACKUP_DIR/daily"/*_full.tar.gz 2>/dev/null); do
local name=$(basename "$file")
local size=$(du -h "$file" | cut -f1)
local date=$(stat -c %y "$file" | cut -d' ' -f1)
printf "%-40s %10s %s\n" "$name" "$size" "$date"
done
echo ""
}
# ---------------------------------------------
# Encontrar backup mas reciente
# ---------------------------------------------
find_latest_backup() {
local type="$1" # db, config, full
local pattern="*_${type}.*gz"
local latest=$(ls -t "$BACKUP_DIR/daily"/$pattern 2>/dev/null | head -1)
if [[ -z "$latest" ]]; then
log_error "No se encontro backup de tipo: $type"
return 1
fi
echo "$latest"
}
# ---------------------------------------------
# Encontrar backup por fecha
# ---------------------------------------------
find_backup_by_date() {
local type="$1"
local date="$2"
local pattern="flotillas_${date}*_${type}.*gz"
local found=$(ls -t "$BACKUP_DIR/daily"/$pattern 2>/dev/null | head -1)
if [[ -z "$found" ]]; then
log_error "No se encontro backup de tipo '$type' para fecha: $date"
return 1
fi
echo "$found"
}
# ---------------------------------------------
# Detener servicios
# ---------------------------------------------
stop_services() {
log_info "Deteniendo servicios..."
systemctl stop flotillas-api 2>/dev/null || true
systemctl stop flotillas-web 2>/dev/null || true
# Esperar a que se detengan
sleep 2
log_success "Servicios detenidos"
}
# ---------------------------------------------
# Iniciar servicios
# ---------------------------------------------
start_services() {
log_info "Iniciando servicios..."
systemctl start flotillas-api 2>/dev/null || true
systemctl start flotillas-web 2>/dev/null || true
log_success "Servicios iniciados"
}
# ---------------------------------------------
# Restaurar base de datos
# ---------------------------------------------
restore_database() {
local backup_file="$1"
if [[ ! -f "$backup_file" ]]; then
log_error "Archivo no encontrado: $backup_file"
return 1
fi
log_info "Restaurando base de datos desde: $(basename "$backup_file")"
# Verificar integridad
log_info "Verificando integridad del archivo..."
if ! gzip -t "$backup_file" 2>/dev/null; then
log_error "El archivo de backup esta corrupto"
return 1
fi
# Confirmar
echo ""
echo -e "${YELLOW}ADVERTENCIA: Esto reemplazara TODOS los datos actuales${NC}"
echo -e "${YELLOW}Base de datos: ${DB_NAME}${NC}"
echo ""
read -p "Continuar? (escribir 'SI' para confirmar): " confirm
if [[ "$confirm" != "SI" ]]; then
log_warn "Restauracion cancelada"
return 1
fi
stop_services
# Exportar password
export PGPASSWORD="$DB_PASSWORD"
# Crear backup de seguridad antes de restaurar
log_info "Creando backup de seguridad..."
local safety_backup="$BACKUP_DIR/temp/pre_restore_$(date +%Y%m%d_%H%M%S).sql.gz"
mkdir -p "$BACKUP_DIR/temp"
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" 2>/dev/null | gzip > "$safety_backup" || true
log_info "Backup de seguridad: $safety_backup"
# Terminar conexiones activas
log_info "Cerrando conexiones activas..."
psql -h "$DB_HOST" -p "$DB_PORT" -U postgres -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '${DB_NAME}' AND pid <> pg_backend_pid();
" 2>/dev/null || true
# Recrear base de datos
log_info "Recreando base de datos..."
# Eliminar y recrear BD
psql -h "$DB_HOST" -p "$DB_PORT" -U postgres <<EOF
DROP DATABASE IF EXISTS ${DB_NAME};
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};
EOF
# Conectar y crear extensiones
psql -h "$DB_HOST" -p "$DB_PORT" -U postgres -d "$DB_NAME" <<EOF
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
GRANT ALL ON DATABASE ${DB_NAME} TO ${DB_USER};
GRANT ALL ON SCHEMA public TO ${DB_USER};
EOF
# Restaurar datos
log_info "Restaurando datos..."
gunzip -c "$backup_file" | psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -q
unset PGPASSWORD
log_success "Base de datos restaurada exitosamente"
start_services
}
# ---------------------------------------------
# Restaurar configuracion
# ---------------------------------------------
restore_config() {
local backup_file="$1"
if [[ ! -f "$backup_file" ]]; then
log_error "Archivo no encontrado: $backup_file"
return 1
fi
log_info "Restaurando configuracion desde: $(basename "$backup_file")"
# Verificar integridad
if ! gzip -t "$backup_file" 2>/dev/null; then
log_error "El archivo de backup esta corrupto"
return 1
fi
# Confirmar
echo ""
echo -e "${YELLOW}ADVERTENCIA: Esto sobrescribira archivos de configuracion${NC}"
echo ""
read -p "Continuar? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_warn "Restauracion cancelada"
return 1
fi
stop_services
# Crear directorio temporal
local temp_dir=$(mktemp -d)
# Extraer
log_info "Extrayendo configuracion..."
tar -xzf "$backup_file" -C "$temp_dir"
# Restaurar archivos
log_info "Restaurando archivos..."
# .env
if [[ -f "$temp_dir$INSTALL_DIR/.env" ]]; then
cp "$temp_dir$INSTALL_DIR/.env" "$INSTALL_DIR/.env"
log_info "Restaurado: .env"
fi
# Traccar
if [[ -f "$temp_dir/opt/traccar/conf/traccar.xml" ]]; then
cp "$temp_dir/opt/traccar/conf/traccar.xml" /opt/traccar/conf/traccar.xml
log_info "Restaurado: traccar.xml"
fi
# MediaMTX
if [[ -f "$temp_dir/opt/mediamtx/mediamtx.yml" ]]; then
cp "$temp_dir/opt/mediamtx/mediamtx.yml" /opt/mediamtx/mediamtx.yml
log_info "Restaurado: mediamtx.yml"
fi
# Servicios systemd
for service in $temp_dir/etc/systemd/system/flotillas-*.service; do
if [[ -f "$service" ]]; then
cp "$service" /etc/systemd/system/
log_info "Restaurado: $(basename "$service")"
fi
done
# Recargar systemd
systemctl daemon-reload
# Limpiar
rm -rf "$temp_dir"
log_success "Configuracion restaurada"
start_services
}
# ---------------------------------------------
# Restaurar backup completo
# ---------------------------------------------
restore_full() {
local backup_file="$1"
if [[ ! -f "$backup_file" ]]; then
log_error "Archivo no encontrado: $backup_file"
return 1
fi
log_info "Restaurando backup completo desde: $(basename "$backup_file")"
echo ""
echo -e "${RED}ADVERTENCIA: Esto reemplazara TODO el directorio de la aplicacion${NC}"
echo ""
read -p "Continuar? (escribir 'SI' para confirmar): " confirm
if [[ "$confirm" != "SI" ]]; then
log_warn "Restauracion cancelada"
return 1
fi
stop_services
# Backup actual
log_info "Respaldando instalacion actual..."
local current_backup="$BACKUP_DIR/temp/pre_restore_full_$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "$current_backup" "$INSTALL_DIR" 2>/dev/null || true
# Extraer
log_info "Extrayendo backup completo..."
tar -xzf "$backup_file" -C /
log_success "Backup completo restaurado"
# Reinstalar dependencias
log_info "Reinstalando dependencias..."
cd "$INSTALL_DIR/backend"
source venv/bin/activate
pip install -r requirements.txt -q 2>/dev/null || pip install -e . -q
deactivate
cd "$INSTALL_DIR/frontend"
npm install 2>/dev/null || true
start_services
}
# ---------------------------------------------
# Main
# ---------------------------------------------
main() {
parse_args "$@"
# Solo listar
if [[ "$LIST_ONLY" == "true" ]]; then
list_backups
exit 0
fi
# Modo interactivo si no se especificaron opciones
if [[ -z "$DB_BACKUP" ]] && [[ -z "$CONFIG_BACKUP" ]] && [[ "$USE_LATEST" != "true" ]] && [[ -z "$RESTORE_DATE" ]]; then
echo ""
echo "Sistema de Flotillas - Restauracion"
echo "===================================="
echo ""
echo "Selecciona una opcion:"
echo " 1) Restaurar backup mas reciente (DB + Config)"
echo " 2) Restaurar solo base de datos"
echo " 3) Restaurar solo configuracion"
echo " 4) Listar backups disponibles"
echo " 5) Cancelar"
echo ""
read -p "Opcion: " option
case $option in
1)
USE_LATEST=true
;;
2)
list_backups
echo ""
read -p "Ingresa ruta del archivo de BD: " DB_BACKUP
;;
3)
list_backups
echo ""
read -p "Ingresa ruta del archivo de config: " CONFIG_BACKUP
;;
4)
list_backups
exit 0
;;
*)
echo "Cancelado"
exit 0
;;
esac
fi
# Restaurar por fecha
if [[ -n "$RESTORE_DATE" ]]; then
log_info "Buscando backups para fecha: $RESTORE_DATE"
DB_BACKUP=$(find_backup_by_date "db.sql" "$RESTORE_DATE") || exit 1
CONFIG_BACKUP=$(find_backup_by_date "config.tar" "$RESTORE_DATE") || true
fi
# Restaurar mas reciente
if [[ "$USE_LATEST" == "true" ]]; then
log_info "Buscando backups mas recientes..."
DB_BACKUP=$(find_latest_backup "db.sql") || exit 1
CONFIG_BACKUP=$(find_latest_backup "config.tar") || true
fi
# Ejecutar restauraciones
if [[ -n "$DB_BACKUP" ]]; then
restore_database "$DB_BACKUP"
fi
if [[ -n "$CONFIG_BACKUP" ]]; then
restore_config "$CONFIG_BACKUP"
fi
echo ""
log_success "=========================================="
log_success "RESTAURACION COMPLETADA"
log_success "=========================================="
echo ""
echo "Verifica que los servicios esten funcionando:"
echo " systemctl status flotillas-api"
echo " systemctl status flotillas-web"
echo ""
}
# Ejecutar
main "$@"

214
deploy/scripts/status.sh Normal file
View File

@@ -0,0 +1,214 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Estado del Sistema
# ============================================
# Muestra informacion completa del estado
# ============================================
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
INSTALL_DIR="${INSTALL_DIR:-/opt/flotillas}"
# Cargar variables
if [[ -f "$INSTALL_DIR/.env" ]]; then
export $(grep -v '^#' "$INSTALL_DIR/.env" | xargs)
fi
clear
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ SISTEMA DE FLOTILLAS - ESTADO DEL SISTEMA ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
# ---------------------------------------------
# Informacion del servidor
# ---------------------------------------------
echo -e "${BLUE}┌─ Servidor ─────────────────────────────────────────────────┐${NC}"
echo -e "│ Hostname: $(hostname)"
echo -e "│ IP: $(hostname -I | awk '{print $1}')"
echo -e "│ Sistema: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo -e "│ Kernel: $(uname -r)"
echo -e "│ Uptime: $(uptime -p)"
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Recursos del sistema
# ---------------------------------------------
echo -e "${BLUE}┌─ Recursos ─────────────────────────────────────────────────┐${NC}"
# CPU
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
echo -e "│ CPU: ${CPU_USAGE}% usado"
# Memoria
MEM_TOTAL=$(free -h | awk '/^Mem:/{print $2}')
MEM_USED=$(free -h | awk '/^Mem:/{print $3}')
MEM_PERCENT=$(free | awk '/^Mem:/{printf "%.1f", $3/$2*100}')
echo -e "│ Memoria: ${MEM_USED} / ${MEM_TOTAL} (${MEM_PERCENT}%)"
# Disco
DISK_USAGE=$(df -h / | awk 'NR==2{print $3 " / " $2 " (" $5 ")"}')
echo -e "│ Disco: ${DISK_USAGE}"
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Servicios
# ---------------------------------------------
echo -e "${BLUE}┌─ Servicios ────────────────────────────────────────────────┐${NC}"
check_service() {
local service="$1"
local name="$2"
local port="$3"
if systemctl is-active --quiet "$service" 2>/dev/null; then
local status="${GREEN}ACTIVO${NC}"
if [[ -n "$port" ]]; then
status="$status (puerto $port)"
fi
else
local status="${RED}INACTIVO${NC}"
fi
printf "│ %-14s %s\n" "$name:" "$status"
}
check_service "flotillas-api" "API Backend" "${API_PORT:-8000}"
check_service "flotillas-web" "Frontend" "${FRONTEND_PORT:-3000}"
check_service "postgresql" "PostgreSQL" "5432"
check_service "redis-server" "Redis" "6379"
check_service "traccar" "Traccar GPS" "${TRACCAR_PORT:-5055}"
check_service "mediamtx" "MediaMTX" "8554"
check_service "mosquitto" "MQTT" "1883"
check_service "cloudflared" "Cloudflare" "-"
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Base de datos
# ---------------------------------------------
echo -e "${BLUE}┌─ Base de Datos ────────────────────────────────────────────┐${NC}"
if systemctl is-active --quiet postgresql; then
# Tamanio de BD
DB_SIZE=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h localhost -U "${POSTGRES_USER:-flotillas}" -d "${POSTGRES_DB:-flotillas}" -t -c "SELECT pg_size_pretty(pg_database_size(current_database()));" 2>/dev/null | xargs)
echo -e "│ Tamanio BD: ${DB_SIZE:-N/A}"
# Conexiones activas
CONNECTIONS=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h localhost -U "${POSTGRES_USER:-flotillas}" -d "${POSTGRES_DB:-flotillas}" -t -c "SELECT count(*) FROM pg_stat_activity WHERE datname = current_database();" 2>/dev/null | xargs)
echo -e "│ Conexiones: ${CONNECTIONS:-N/A} activas"
# Posiciones (si existe la tabla)
POSITIONS=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h localhost -U "${POSTGRES_USER:-flotillas}" -d "${POSTGRES_DB:-flotillas}" -t -c "SELECT COUNT(*) FROM positions;" 2>/dev/null | xargs)
if [[ -n "$POSITIONS" ]]; then
echo -e "│ Posiciones: ${POSITIONS} registros"
fi
else
echo -e "${RED}PostgreSQL no esta activo${NC}"
fi
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Redis
# ---------------------------------------------
echo -e "${BLUE}┌─ Redis Cache ──────────────────────────────────────────────┐${NC}"
if systemctl is-active --quiet redis-server; then
REDIS_AUTH=""
[[ -n "${REDIS_PASSWORD}" ]] && REDIS_AUTH="-a ${REDIS_PASSWORD}"
REDIS_KEYS=$(redis-cli $REDIS_AUTH DBSIZE 2>/dev/null | awk '{print $2}')
REDIS_MEM=$(redis-cli $REDIS_AUTH INFO memory 2>/dev/null | grep used_memory_human | cut -d: -f2 | tr -d '\r')
echo -e "│ Keys: ${REDIS_KEYS:-N/A}"
echo -e "│ Memoria: ${REDIS_MEM:-N/A}"
else
echo -e "${RED}Redis no esta activo${NC}"
fi
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Unidades GPS activas
# ---------------------------------------------
echo -e "${BLUE}┌─ GPS / Unidades ───────────────────────────────────────────┐${NC}"
if systemctl is-active --quiet postgresql; then
# Total de unidades
TOTAL_UNITS=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h localhost -U "${POSTGRES_USER:-flotillas}" -d "${POSTGRES_DB:-flotillas}" -t -c "SELECT COUNT(*) FROM units;" 2>/dev/null | xargs)
# Unidades activas (con posicion en ultimos 5 min)
ACTIVE_UNITS=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h localhost -U "${POSTGRES_USER:-flotillas}" -d "${POSTGRES_DB:-flotillas}" -t -c "SELECT COUNT(DISTINCT unit_id) FROM positions WHERE device_time > NOW() - INTERVAL '5 minutes';" 2>/dev/null | xargs)
echo -e "│ Total: ${TOTAL_UNITS:-0} unidades"
echo -e "│ Activas: ${ACTIVE_UNITS:-0} (ultimo 5 min)"
else
echo -e "${YELLOW}No se puede obtener info de unidades${NC}"
fi
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Streaming
# ---------------------------------------------
echo -e "${BLUE}┌─ Video Streaming ──────────────────────────────────────────┐${NC}"
if systemctl is-active --quiet mediamtx; then
# Obtener streams activos de MediaMTX API
STREAMS=$(curl -s http://localhost:9997/v3/paths/list 2>/dev/null | jq '.items | length' 2>/dev/null)
echo -e "│ Streams: ${STREAMS:-0} activos"
# Endpoints
echo -e "│ RTSP: rtsp://localhost:8554"
echo -e "│ WebRTC: http://localhost:8889"
echo -e "│ HLS: http://localhost:8888"
else
echo -e "${RED}MediaMTX no esta activo${NC}"
fi
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Ultimos errores
# ---------------------------------------------
echo -e "${BLUE}┌─ Ultimos Errores (API) ────────────────────────────────────┐${NC}"
ERRORS=$(journalctl -u flotillas-api --since "1 hour ago" -p err --no-pager -q 2>/dev/null | tail -3)
if [[ -z "$ERRORS" ]]; then
echo -e "${GREEN}Sin errores en la ultima hora${NC}"
else
echo "$ERRORS" | while read line; do
echo "$line" | cut -c1-65
done
fi
echo -e "${BLUE}└─────────────────────────────────────────────────────────────┘${NC}"
echo ""
# ---------------------------------------------
# Comandos utiles
# ---------------------------------------------
echo -e "${YELLOW}Comandos utiles:${NC}"
echo " ./health-check.sh - Verificar salud del sistema"
echo " ./logs.sh api -f - Ver logs de API en tiempo real"
echo " ./backup.sh - Crear backup"
echo " ./update.sh - Actualizar sistema"
echo ""

485
deploy/scripts/update.sh Normal file
View File

@@ -0,0 +1,485 @@
#!/bin/bash
# ============================================
# Sistema de Flotillas - Script de Actualizacion
# ============================================
# Actualiza la aplicacion a la ultima version
#
# Uso: ./update.sh [--branch BRANCH] [--force] [--no-backup]
#
# Opciones:
# --branch Branch a usar (default: main)
# --force Forzar actualizacion (descartar cambios locales)
# --no-backup No crear backup antes de actualizar
# --backend Solo actualizar backend
# --frontend Solo actualizar frontend
# ============================================
set -e
set -o pipefail
# ---------------------------------------------
# Colores
# ---------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ---------------------------------------------
# Variables
# ---------------------------------------------
INSTALL_DIR="${INSTALL_DIR:-/opt/flotillas}"
REPO_BRANCH="${REPO_BRANCH:-main}"
BACKUP_BEFORE_UPDATE=true
FORCE_UPDATE=false
UPDATE_BACKEND=true
UPDATE_FRONTEND=true
# Cargar variables de entorno
if [[ -f "$INSTALL_DIR/.env" ]]; then
export $(grep -v '^#' "$INSTALL_DIR/.env" | xargs)
fi
# ---------------------------------------------
# Funciones
# ---------------------------------------------
log_info() {
echo -e "${BLUE}[$(date '+%H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%H:%M:%S')] [OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%H:%M:%S')] [ERROR]${NC} $1"
}
# ---------------------------------------------
# Parsear argumentos
# ---------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--branch)
REPO_BRANCH="$2"
shift 2
;;
--force)
FORCE_UPDATE=true
shift
;;
--no-backup)
BACKUP_BEFORE_UPDATE=false
shift
;;
--backend)
UPDATE_FRONTEND=false
shift
;;
--frontend)
UPDATE_BACKEND=false
shift
;;
--help|-h)
echo "Uso: $0 [--branch BRANCH] [--force] [--no-backup]"
echo ""
echo "Opciones:"
echo " --branch Branch a usar (default: main)"
echo " --force Forzar (descartar cambios locales)"
echo " --no-backup No crear backup antes de actualizar"
echo " --backend Solo actualizar backend"
echo " --frontend Solo actualizar frontend"
exit 0
;;
*)
log_error "Opcion desconocida: $1"
exit 1
;;
esac
done
}
# ---------------------------------------------
# Verificar requisitos
# ---------------------------------------------
check_requirements() {
log_info "Verificando requisitos..."
# Verificar directorio
if [[ ! -d "$INSTALL_DIR" ]]; then
log_error "Directorio de instalacion no encontrado: $INSTALL_DIR"
exit 1
fi
# Verificar git
if [[ ! -d "$INSTALL_DIR/.git" ]]; then
log_error "No es un repositorio git"
exit 1
fi
# Verificar conexion
if ! ping -c 1 github.com &> /dev/null; then
log_error "Sin conexion a internet"
exit 1
fi
log_success "Requisitos OK"
}
# ---------------------------------------------
# Crear backup
# ---------------------------------------------
create_backup() {
if [[ "$BACKUP_BEFORE_UPDATE" != "true" ]]; then
log_warn "Saltando backup (--no-backup)"
return
fi
log_info "Creando backup antes de actualizar..."
if [[ -f "$INSTALL_DIR/deploy/scripts/backup.sh" ]]; then
bash "$INSTALL_DIR/deploy/scripts/backup.sh" || log_warn "Backup fallo, continuando..."
else
log_warn "Script de backup no encontrado"
fi
}
# ---------------------------------------------
# Obtener version actual
# ---------------------------------------------
get_current_version() {
cd "$INSTALL_DIR"
# Intentar obtener version de git tag
local version=$(git describe --tags --always 2>/dev/null || echo "unknown")
# O del commit
local commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
echo "${version} (${commit})"
}
# ---------------------------------------------
# Verificar cambios locales
# ---------------------------------------------
check_local_changes() {
cd "$INSTALL_DIR"
if [[ -n $(git status --porcelain) ]]; then
if [[ "$FORCE_UPDATE" == "true" ]]; then
log_warn "Descartando cambios locales..."
git reset --hard HEAD
git clean -fd
else
log_error "Hay cambios locales sin commitear"
log_error "Usa --force para descartarlos"
git status --short
exit 1
fi
fi
}
# ---------------------------------------------
# Actualizar codigo
# ---------------------------------------------
update_code() {
log_info "Actualizando codigo desde repositorio..."
cd "$INSTALL_DIR"
# Guardar version actual
local old_version=$(get_current_version)
# Fetch
log_info "Obteniendo cambios..."
git fetch origin
# Verificar si hay actualizaciones
local local_hash=$(git rev-parse HEAD)
local remote_hash=$(git rev-parse origin/${REPO_BRANCH})
if [[ "$local_hash" == "$remote_hash" ]]; then
log_success "Ya estas en la version mas reciente"
return 0
fi
# Mostrar cambios pendientes
log_info "Cambios disponibles:"
git log --oneline HEAD..origin/${REPO_BRANCH} | head -10
# Actualizar
log_info "Aplicando cambios..."
git reset --hard origin/${REPO_BRANCH}
local new_version=$(get_current_version)
log_success "Codigo actualizado: $old_version -> $new_version"
return 1 # Indica que hubo cambios
}
# ---------------------------------------------
# Actualizar backend
# ---------------------------------------------
update_backend() {
if [[ "$UPDATE_BACKEND" != "true" ]]; then
log_info "Saltando actualizacion de backend"
return
fi
log_info "Actualizando backend..."
cd "$INSTALL_DIR/backend"
# Activar entorno virtual
source venv/bin/activate
# Actualizar dependencias
log_info "Instalando dependencias Python..."
if [[ -f "requirements.txt" ]]; then
pip install -r requirements.txt -q --upgrade
elif [[ -f "pyproject.toml" ]]; then
pip install -e . -q --upgrade
fi
# Ejecutar migraciones
log_info "Ejecutando migraciones..."
if [[ -d "alembic" ]]; then
alembic upgrade head 2>/dev/null || log_warn "Sin migraciones pendientes"
fi
deactivate
log_success "Backend actualizado"
}
# ---------------------------------------------
# Actualizar frontend
# ---------------------------------------------
update_frontend() {
if [[ "$UPDATE_FRONTEND" != "true" ]]; then
log_info "Saltando actualizacion de frontend"
return
fi
log_info "Actualizando frontend..."
cd "$INSTALL_DIR/frontend"
# Verificar package.json
if [[ ! -f "package.json" ]]; then
log_warn "No hay package.json en frontend"
return
fi
# Instalar dependencias
log_info "Instalando dependencias Node.js..."
if command -v pnpm &> /dev/null; then
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
else
npm ci 2>/dev/null || npm install
fi
# Build
log_info "Compilando frontend..."
if command -v pnpm &> /dev/null; then
pnpm build
else
npm run build
fi
log_success "Frontend actualizado"
}
# ---------------------------------------------
# Reiniciar servicios
# ---------------------------------------------
restart_services() {
log_info "Reiniciando servicios..."
if [[ "$UPDATE_BACKEND" == "true" ]]; then
systemctl restart flotillas-api 2>/dev/null && log_success "flotillas-api reiniciado" || log_warn "flotillas-api no existe"
fi
if [[ "$UPDATE_FRONTEND" == "true" ]]; then
systemctl restart flotillas-web 2>/dev/null && log_success "flotillas-web reiniciado" || log_warn "flotillas-web no existe"
fi
}
# ---------------------------------------------
# Verificar servicios
# ---------------------------------------------
verify_services() {
log_info "Verificando servicios..."
local all_ok=true
# Esperar a que inicien
sleep 3
if [[ "$UPDATE_BACKEND" == "true" ]]; then
if systemctl is-active --quiet flotillas-api; then
log_success "flotillas-api: activo"
else
log_error "flotillas-api: inactivo"
all_ok=false
fi
fi
if [[ "$UPDATE_FRONTEND" == "true" ]]; then
if systemctl is-active --quiet flotillas-web; then
log_success "flotillas-web: activo"
else
log_error "flotillas-web: inactivo"
all_ok=false
fi
fi
# Verificar API health
if [[ "$UPDATE_BACKEND" == "true" ]]; then
local api_port="${API_PORT:-8000}"
if curl -s "http://localhost:${api_port}/health" > /dev/null 2>&1; then
log_success "API health check: OK"
else
log_warn "API health check: no responde (puede estar iniciando)"
fi
fi
if [[ "$all_ok" == "false" ]]; then
log_error "Algunos servicios fallaron. Revisa los logs:"
echo " journalctl -u flotillas-api -n 50"
echo " journalctl -u flotillas-web -n 50"
return 1
fi
return 0
}
# ---------------------------------------------
# Limpiar cache
# ---------------------------------------------
clean_cache() {
log_info "Limpiando cache..."
# Python cache
find "$INSTALL_DIR/backend" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find "$INSTALL_DIR/backend" -type f -name "*.pyc" -delete 2>/dev/null || true
# Node cache
rm -rf "$INSTALL_DIR/frontend/.next/cache" 2>/dev/null || true
log_success "Cache limpiado"
}
# ---------------------------------------------
# Rollback
# ---------------------------------------------
rollback() {
local previous_commit="$1"
log_warn "Ejecutando rollback a: $previous_commit"
cd "$INSTALL_DIR"
git reset --hard "$previous_commit"
update_backend
update_frontend
restart_services
log_success "Rollback completado"
}
# ---------------------------------------------
# Mostrar resumen
# ---------------------------------------------
show_summary() {
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} ACTUALIZACION COMPLETADA${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Version actual: $(get_current_version)"
echo "Branch: $REPO_BRANCH"
echo ""
echo "Servicios:"
systemctl is-active flotillas-api 2>/dev/null && echo " - flotillas-api: activo" || echo " - flotillas-api: inactivo"
systemctl is-active flotillas-web 2>/dev/null && echo " - flotillas-web: activo" || echo " - flotillas-web: inactivo"
echo ""
}
# ---------------------------------------------
# Main
# ---------------------------------------------
main() {
parse_args "$@"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} ACTUALIZANDO SISTEMA DE FLOTILLAS${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo "Branch: $REPO_BRANCH"
echo "Force: $FORCE_UPDATE"
echo "Backup: $BACKUP_BEFORE_UPDATE"
echo ""
# Guardar commit actual para posible rollback
cd "$INSTALL_DIR"
PREVIOUS_COMMIT=$(git rev-parse HEAD)
check_requirements
create_backup
check_local_changes
# Intentar actualizar
if update_code; then
log_info "No hay actualizaciones disponibles"
exit 0
fi
# Actualizar componentes
update_backend || {
log_error "Fallo en backend, haciendo rollback..."
rollback "$PREVIOUS_COMMIT"
exit 1
}
update_frontend || {
log_error "Fallo en frontend, haciendo rollback..."
rollback "$PREVIOUS_COMMIT"
exit 1
}
clean_cache
restart_services
# Verificar
if ! verify_services; then
echo ""
read -p "Hacer rollback? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
rollback "$PREVIOUS_COMMIT"
exit 1
fi
fi
show_summary
}
# Manejo de errores
trap 'log_error "Error en linea $LINENO"; exit 1' ERR
# Ejecutar
main "$@"