#!/bin/bash ############################################################# # DEPLOY SCRIPT PARA VPS - WhatsApp Bot # # Uso: ./deploy-vps.sh [opciones] # # Opciones: # --install Primera instalación (instala Docker, etc) # --update Solo actualizar código y reiniciar # --ssl Generar/renovar certificados SSL # --logs Ver logs de la aplicación # --status Ver estado de los contenedores ############################################################# set -e # ==================== CONFIGURACIÓN ==================== # Modificar estos valores según tu VPS DOMAIN="bot.u-s.app" # Tu dominio EMAIL="admin@u-s.app" # Email para Let's Encrypt APP_DIR="/var/www/whatsapp" # Directorio de la app en VPS COMPOSE_FILE="docker-compose.prod.yml" # Colores para output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ==================== 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" } # Verificar que se ejecuta como root o con sudo check_root() { if [ "$EUID" -ne 0 ]; then log_error "Este script debe ejecutarse como root o con sudo" exit 1 fi } # Instalar Docker y Docker Compose install_docker() { log_info "Instalando Docker..." # Verificar si Docker ya está instalado if command -v docker &> /dev/null; then log_success "Docker ya está instalado: $(docker --version)" else curl -fsSL https://get.docker.com | sh systemctl enable docker systemctl start docker log_success "Docker instalado correctamente" fi # Verificar Docker Compose if command -v docker-compose &> /dev/null || docker compose version &> /dev/null; then log_success "Docker Compose disponible" else log_info "Instalando Docker Compose plugin..." apt-get update apt-get install -y docker-compose-plugin log_success "Docker Compose instalado" fi } # Instalar Certbot para SSL install_certbot() { log_info "Instalando Certbot para SSL..." if command -v certbot &> /dev/null; then log_success "Certbot ya está instalado" else apt-get update apt-get install -y certbot log_success "Certbot instalado" fi } # Generar certificados SSL con Let's Encrypt generate_ssl() { log_info "Generando certificados SSL para ${DOMAIN}..." # Detener contenedores que usan puerto 80 cd "$APP_DIR" docker compose -f "$COMPOSE_FILE" down 2>/dev/null || true # Generar certificado certbot certonly \ --standalone \ --preferred-challenges http \ --agree-tos \ --email "$EMAIL" \ -d "$DOMAIN" \ --non-interactive if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then log_success "Certificado SSL generado correctamente" # Crear directorio y copiar certificados mkdir -p "$APP_DIR/docker/ssl" cp "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" "$APP_DIR/docker/ssl/server.crt" cp "/etc/letsencrypt/live/${DOMAIN}/privkey.pem" "$APP_DIR/docker/ssl/server.key" chmod 600 "$APP_DIR/docker/ssl/server.key" log_success "Certificados copiados a docker/ssl/" else log_error "Error generando certificado SSL" exit 1 fi } # Renovar certificados SSL renew_ssl() { log_info "Renovando certificados SSL..." cd "$APP_DIR" docker compose -f "$COMPOSE_FILE" stop app 2>/dev/null || true certbot renew --standalone # Copiar certificados actualizados if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then cp "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" "$APP_DIR/docker/ssl/server.crt" cp "/etc/letsencrypt/live/${DOMAIN}/privkey.pem" "$APP_DIR/docker/ssl/server.key" log_success "Certificados renovados" fi docker compose -f "$COMPOSE_FILE" start app } # Configurar cron para renovación automática de SSL setup_ssl_cron() { log_info "Configurando renovación automática de SSL..." CRON_CMD="0 3 * * * cd $APP_DIR && docker compose -f $COMPOSE_FILE stop app && certbot renew --quiet && docker compose -f $COMPOSE_FILE start app" # Añadir al crontab si no existe (crontab -l 2>/dev/null | grep -v "certbot renew"; echo "$CRON_CMD") | crontab - log_success "Cron configurado para renovar SSL cada día a las 3am" } # Crear archivo .env si no existe create_env_file() { if [ ! -f "$APP_DIR/.env" ]; then log_info "Creando archivo .env..." cat > "$APP_DIR/.env" << 'EOF' # ============================================ # CONFIGURACIÓN DE PRODUCCIÓN # ============================================ # Base de datos DB_HOST=77.37.126.126 DB_PORT=3306 DB_NAME=usite_whatsapp_bot DB_USER=usite_whatsapp_user DB_PASS=6q!Vio0fn@PFmsLyHjH5 # Redis (opcional, dejar vacío si no usa password) REDIS_PASSWORD= # WhatsApp API (obtener de Facebook Developer) WHATSAPP_TOKEN= WHATSAPP_PHONE_NUMBER_ID= WEBHOOK_VERIFY_TOKEN= EOF log_warn "Archivo .env creado. ¡EDITA LAS CREDENCIALES antes de continuar!" log_warn "Ejecuta: nano $APP_DIR/.env" else log_success "Archivo .env ya existe" fi } # Crear directorios necesarios create_directories() { log_info "Creando directorios..." mkdir -p "$APP_DIR/logs" mkdir -p "$APP_DIR/uploads" mkdir -p "$APP_DIR/docker/ssl" # Permisos chown -R www-data:www-data "$APP_DIR/logs" 2>/dev/null || true chown -R www-data:www-data "$APP_DIR/uploads" 2>/dev/null || true chmod -R 755 "$APP_DIR/logs" chmod -R 755 "$APP_DIR/uploads" log_success "Directorios creados" } # Construir y levantar contenedores deploy() { log_info "Desplegando aplicación..." cd "$APP_DIR" # Construir imágenes log_info "Construyendo imágenes Docker..." docker compose -f "$COMPOSE_FILE" build --no-cache # Detener contenedores existentes (NO borra volúmenes, los datos persisten) log_info "Deteniendo contenedores anteriores..." docker compose -f "$COMPOSE_FILE" down --remove-orphans 2>/dev/null || true # Levantar nuevos contenedores log_info "Levantando contenedores..." docker compose -f "$COMPOSE_FILE" up -d # Esperar a que estén listos log_info "Esperando a que los servicios estén listos..." sleep 10 # Verificar estado if docker compose -f "$COMPOSE_FILE" ps | grep -q "Up"; then log_success "Contenedores ejecutándose correctamente" else log_error "Error iniciando contenedores" docker compose -f "$COMPOSE_FILE" logs --tail=50 exit 1 fi } # Actualizar código y reiniciar update() { log_info "Actualizando aplicación..." cd "$APP_DIR" # Si es un repo git, hacer pull if [ -d ".git" ]; then log_info "Actualizando desde Git..." git pull origin main || git pull origin master fi # Reconstruir y reiniciar docker compose -f "$COMPOSE_FILE" build docker compose -f "$COMPOSE_FILE" up -d log_success "Aplicación actualizada" } # Ver logs show_logs() { cd "$APP_DIR" docker compose -f "$COMPOSE_FILE" logs -f --tail=100 } # Ver estado show_status() { cd "$APP_DIR" echo "" echo "==================== ESTADO DE CONTENEDORES ====================" docker compose -f "$COMPOSE_FILE" ps echo "" echo "==================== USO DE RECURSOS ====================" docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" echo "" echo "==================== VERIFICACIÓN DE SERVICIOS ====================" # Verificar HTTP HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost/health.php" 2>/dev/null || echo "ERROR") if [ "$HTTP_STATUS" = "200" ]; then log_success "HTTP: OK (status $HTTP_STATUS)" else log_error "HTTP: FALLO (status $HTTP_STATUS)" fi # Verificar HTTPS HTTPS_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://localhost/health.php" 2>/dev/null || echo "ERROR") if [ "$HTTPS_STATUS" = "200" ]; then log_success "HTTPS: OK (status $HTTPS_STATUS)" else log_warn "HTTPS: $HTTPS_STATUS (puede necesitar certificados SSL)" fi echo "" echo "==================== URLS ====================" echo " App: https://${DOMAIN}" echo " Health: https://${DOMAIN}/health.php" } # Primera instalación completa full_install() { check_root echo "" echo "==========================================" echo " INSTALACIÓN COMPLETA - WhatsApp Bot" echo "==========================================" echo "" install_docker install_certbot create_directories create_env_file echo "" log_warn "ANTES DE CONTINUAR:" log_warn "1. Edita el archivo .env con tus credenciales" log_warn "2. Asegúrate que el dominio ${DOMAIN} apunta a este servidor" echo "" read -p "¿Continuar con el deploy? (s/n): " -n 1 -r echo "" if [[ $REPLY =~ ^[Ss]$ ]]; then generate_ssl setup_ssl_cron deploy show_status else log_info "Deploy cancelado. Ejecuta './deploy-vps.sh' cuando estés listo." fi } # Mostrar ayuda show_help() { echo "" echo "Uso: ./deploy-vps.sh [opción]" echo "" echo "Opciones:" echo " --install Primera instalación completa" echo " --update Actualizar código y reiniciar" echo " --ssl Generar/renovar certificados SSL" echo " --logs Ver logs en tiempo real" echo " --status Ver estado de servicios" echo " --help Mostrar esta ayuda" echo "" echo "Ejemplos:" echo " sudo ./deploy-vps.sh --install # Primera vez" echo " sudo ./deploy-vps.sh --update # Actualizar" echo " ./deploy-vps.sh --logs # Ver logs" echo "" } # ==================== MAIN ==================== case "${1:-}" in --install) full_install ;; --update) update ;; --ssl) check_root generate_ssl ;; --renew-ssl) check_root renew_ssl ;; --logs) show_logs ;; --status) show_status ;; --help|-h) show_help ;; *) # Sin argumentos, hacer deploy normal if [ ! -d "$APP_DIR" ] || [ ! -f "$APP_DIR/$COMPOSE_FILE" ]; then log_error "Directorio de aplicación no encontrado: $APP_DIR" log_info "Para primera instalación usa: sudo ./deploy-vps.sh --install" exit 1 fi deploy show_status ;; esac