resources/views/pages/frontend/docs/deploy-automatizado.md
Última modificación: 27 Oct, 2026

Automatización de Despliegue Continuo (CD) desde Bastión

En una arquitectura distribuida de múltiples servidores (Nodos Web, Nodo Worker y Nodo Realtime), entrar manualmente por SSH servidor por servidor para ejecutar git pull provoca inconsistencias de versiones (Race Conditions) y errores humanos.

Mañuti convierte al Nodo Load Balancer (LB) en un Bastión de Despliegue Centralizado. Desde este único punto se inyecta el archivo maestro de configuración (.env.master), se sincroniza el código fuente en paralelo y se reinician selectivamente los demonios correspondientes a cada rol en menos de 5 segundos.


1. Relación de Confianza Criptográfica (SSH Handshake)

Ejecuta estos pasos exclusivamente en el Nodo Load Balancer (Bastión):

1.1. Generar Llave Maestra Ed25519 sin Passphrase

sudo apt update && sudo apt install -y openssh-client
ssh-keygen -t ed25519 -C "deploy@manuti-cluster" -f ~/.ssh/id_ed25519_deploy -N ""

1.2. Propagar la Llave hacia los Nodos de Aplicación

# Propagar a Nodos Web
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub [email protected]
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub [email protected]

# Propagar a Nodo Worker (Horizon)
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub [email protected]

# Propagar a Nodo Realtime (Reverb)
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub [email protected]

2. Permisos Sudo Granulares (sudoers.d) en Nodos Destino

Para que el script automatizado pueda recargar php8.5-fpm, horizon o reverb sin detenerse a pedir contraseña interactiva, aplica la siguiente regla de mínimo privilegio en cada nodo según su rol:

  • En Nodos WEB (.64, .130):
    sudo visudo -f /etc/sudoers.d/deployer
    # Agregar:
    manuelaguado ALL=(ALL) NOPASSWD: /usr/sbin/service php8.5-fpm reload, /usr/bin/systemctl reload php8.5-fpm
    
  • En Nodo WORKER (.126):
    sudo visudo -f /etc/sudoers.d/deployer
    # Agregar:
    manuelaguado ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl restart horizon, /bin/supervisorctl restart horizon
    
  • En Nodo REALTIME (.127):
    sudo visudo -f /etc/sudoers.d/deployer
    # Agregar:
    manuelaguado ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl restart reverb, /bin/supervisorctl restart reverb
    

3. El Archivo Maestro .env.master (Single Source of Truth)

En el Nodo Load Balancer, crea el archivo ~/scripts/.env.master. Este archivo contiene todas las IPs privadas y credenciales de producción del clúster. Cada vez que despliegues, el script inyectará este archivo vía scp en todos los nodos antes de cachear la configuración.

mkdir -p ~/scripts
nano ~/scripts/.env.master

4. Script Maestro de Orquestación Paralela (deploy.sh)

Crea el ejecutable en el Nodo Load Balancer (~/scripts/deploy.sh) y asígnale permisos con chmod +x ~/scripts/deploy.sh:

#!/bin/bash

# ==========================================
#  MAÑUTI / FRAMEDEV - DISTRIBUTED DEPLOY v2.0
# ==========================================

# --- CONFIGURACIÓN ---
REMOTE_USER="manuelaguado"
SSH_KEY="~/.ssh/id_ed25519_deploy"
PROJECT_PATH="/var/www/framedev12"
LOCAL_ENV_PATH="/home/manuelaguado/scripts/.env.master"
TARGET_BRANCH="architecture/dist-v1"

# --- INVENTARIO DE NODOS ---
WEB_NODES=("192.168.1.64" "192.168.1.130")
WORKER_NODES=("192.168.1.126")
REALTIME_NODES=("192.168.1.127")

# Colores
GREEN='\033[0;32m'
CYAN='\033[0;36m'
RED='\033[0;31m'
NC='\033[0m'

echo -e "${CYAN}🚀 INICIANDO DESPLIEGUE DISTRIBUIDO + SINCRONIZACIÓN ENV...${NC}"
START_TIME=$(date +%s)

# Verificación de Seguridad
if [ ! -f "$LOCAL_ENV_PATH" ]; then
    echo -e "${RED}❌ ERROR CRÍTICO: No se encuentra el archivo maestro .env en $LOCAL_ENV_PATH${NC}"
    exit 1
fi

deploy_code() {
    local IP=$1
    local ROLE=$2
    echo -e "  Encargando nodo ${CYAN}$ROLE ($IP)${NC}..."
    
    # 1. INYECCIÓN DE .ENV MAESTRO VÍA SCP
    scp -i $SSH_KEY -o LogLevel=QUIET $LOCAL_ENV_PATH $REMOTE_USER@$IP:$PROJECT_PATH/.env
    
    # 2. SINCRONIZACIÓN GIT Y RECONSTRUCCIÓN DE CACHÉ
    ssh -i $SSH_KEY $REMOTE_USER@$IP <<EOF
        set -e
        cd $PROJECT_PATH
        chmod 640 .env
        
        git fetch origin $TARGET_BRANCH
        git reset --hard origin/$TARGET_BRANCH
        
        php artisan optimize:clear
        php artisan config:cache
        php artisan route:cache
        php artisan view:cache
EOF
}

restart_web() {
    ssh -i $SSH_KEY $REMOTE_USER@$1 "sudo systemctl reload php8.5-fpm"
}

restart_worker() {
    ssh -i $SSH_KEY $REMOTE_USER@$1 "sudo supervisorctl restart horizon"
}

restart_realtime() {
    ssh -i $SSH_KEY $REMOTE_USER@$1 "sudo supervisorctl restart reverb"
}

# --- EJECUCIÓN PARALELA ASÍNCRONA (&) ---

for IP in "${WEB_NODES[@]}"; do
    (
        deploy_code $IP "WEB"
        restart_web $IP
        echo -e "${GREEN}  ✅ NODO WEB $IP ACTUALIZADO.${NC}"
    ) &
done

for IP in "${WORKER_NODES[@]}"; do
    (
        deploy_code $IP "WORKER"
        restart_worker $IP
        echo -e "${GREEN}  ✅ NODO WORKER $IP ACTUALIZADO.${NC}"
    ) &
done

for IP in "${REALTIME_NODES[@]}"; do
    (
        deploy_code $IP "REALTIME"
        restart_realtime $IP
        echo -e "${GREEN}  ✅ NODO REALTIME $IP ACTUALIZADO.${NC}"
    ) &
done

wait
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo -e "${GREEN}✨ SISTEMA SINCRONIZADO EN ${DURATION} SEGUNDOS.${NC}"
Edita este documento en >> deploy-automatizado.md
← Volver a la Landing Page