commit 1cd9237f48fa9bb0da4d70a2b1656c83571e265d Author: Luis Eduardo Perez Date: Wed Jun 10 15:04:29 2026 -0400 feat: BDT CLI v2.0.0 — clone, build, deploy y monitoreo de la plataforma Incluye asistente interactivo 'construir' (git banco -> build -> docker -> up), deteccion automatica del remote del banco, configuracion via ~/.bdt/config y menu interactivo resistente a errores. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fba08e --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# BDT CLI (`bdt`) + +Shell de operaciones de la plataforma BDT: clona los repositorios desde el GitLab del banco, compila los servicios, construye sus imágenes Docker y los levanta con docker compose. Incluye menú interactivo y asistente guiado de construcción. + +## Requisitos + +- bash 3.2+ (macOS o Linux) +- git +- Docker + Docker Compose v2 +- Java 21 (JDK) — para compilar gateway / service-bus +- Node.js 20 — para el frontend (solo si se compila fuera de Docker) + +## Instalación + +```bash +git clone https://git.dcgeeks.net/BDT/bdt-cli.git +cd bdt-cli +./bdt.sh setup # instala el comando global 'bdt' + autocompletado + alias +``` + +O descarga directa del script: + +```bash +curl -fsSL https://git.dcgeeks.net/BDT/bdt-cli/raw/branch/main/bdt.sh -o bdt.sh +chmod +x bdt.sh +./bdt.sh setup +``` + +> `setup` guarda la raíz del proyecto en `~/.bdt/project-root`. Ejecutalo desde el directorio donde van a vivir los repositorios de la plataforma (el script clona `backend/`, `frontend/`, `infrastructure/`, etc. relativo a esa raíz). + +## Configuración + +El script lee `~/.bdt/config` al arrancar (las variables de entorno tienen prioridad): + +```bash +# GitLab del banco +GITLAB_URL=http://gitlab.bicentenariobu.com.ve +GITLAB_GROUP=dcgeeks + +# Ambiente por defecto (desarrollo|calidad|demo|produccion) +DEFAULT_ENV=desarrollo + +# Servidor de deploy remoto (comando server-deploy) +DEPLOY_SERVER= +DEPLOY_USER= +``` + +Credenciales git (para que `clone`/`pull` no pidan contraseña): + +```bash +bdt git-auth +``` + +## Uso rápido + +```bash +bdt # menú interactivo +bdt construir # asistente: elegir servicio + rama → git → build → docker → up +bdt deploy # pipeline completo: pull → compilar → imágenes → up → health +bdt status # estado de servicios y contenedores +bdt health # health check completo +bdt logs gateway -f # seguir logs de un servicio +bdt help # ayuda completa +``` + +### Asistente `construir` (opción `0` del menú) + +Flujo guiado para construir y desplegar **un** servicio: + +1. Pregunta qué servicio construir (frontend, gateway, service-bus, canales-admin-web, canales-worker) mostrando rama por defecto y estado del contenedor. +2. Pregunta la rama (Enter = rama por defecto del servicio). +3. Muestra el plan y pide confirmación. +4. Ejecuta: descarga de la rama desde el GitLab del banco → `gradle bootJar` (solo servicios Java) → `docker build` → `docker compose up -d --force-recreate` → health check. + +Si hay cambios locales sin commitear, ofrece guardarlos en stash antes de cambiar de rama. + +## Servicios gestionados + +| Servicio | Stack | Rama default | Puerto | +|---|---|---|---| +| frontend | Next.js | develop | 59002 | +| gateway | Spring Boot | develop | 58050 | +| service-bus | Spring Boot | develop | 58060 | +| canales-admin-web | Next.js (pnpm) | master | 58180 | +| canales-worker | Node (pnpm) | master | 59091 | + +Infraestructura (postgres, redis, redpanda, keycloak, mailpit, prometheus, grafana) se levanta con `bdt up infra`. + +## Notas + +- Los servicios Java se compilan con `gradle bootJar` **antes** del `docker build` (el Dockerfile copia `build/libs/*.jar`). Frontend y canales compilan dentro del Docker build (multi-stage). +- El nombre del remote que apunta al GitLab del banco puede variar por repositorio; el script lo detecta automáticamente por URL. +- Logs de compilación en `/logs/`. diff --git a/bdt.sh b/bdt.sh new file mode 100755 index 0000000..5568edb --- /dev/null +++ b/bdt.sh @@ -0,0 +1,1768 @@ +#!/bin/bash +#=============================================================================== +# BDT PLATFORM - Shell de Deployment y Operaciones para Servidor +#=============================================================================== +# Uso: ./bdt.sh [comando] [opciones] +# Ejecutar sin argumentos para ver la ayuda completa. +# +# Este script gestiona TODO el ciclo de vida del sistema BDT: +# - Clonar/actualizar repositorios desde GitLab +# - Instalar dependencias y compilar todos los servicios +# - Levantar/detener infraestructura y aplicaciones +# - Monitorear logs en tiempo real +# - Verificar salud de todos los servicios +# - Gestionar ambientes (desarrollo, calidad, demo, produccion) +#=============================================================================== + +set -euo pipefail + +#=============================================================================== +# CONFIGURACION +#=============================================================================== + +SCRIPT_VERSION="2.0.0" +SCRIPT_NAME="$(basename "$0")" + +# Directorios - resolver symlinks para encontrar la ruta real +_resolve_root() { + local source="${BASH_SOURCE[0]}" + # Seguir symlinks hasta llegar al archivo real + while [[ -L "$source" ]]; do + local dir="$(cd "$(dirname "$source")" && pwd)" + source="$(readlink "$source")" + # Si readlink devolvió ruta relativa, resolver contra el directorio + [[ "$source" != /* ]] && source="$dir/$source" + done + cd "$(dirname "$source")" && pwd +} +BDT_ROOT="${BDT_ROOT:-$(_resolve_root)}" + +# Si existe ~/.bdt/project-root, usarlo como override +if [[ -f "${HOME}/.bdt/project-root" ]]; then + _saved_root="$(cat "${HOME}/.bdt/project-root")" + if [[ -d "$_saved_root" && -f "$_saved_root/bdt.sh" ]]; then + BDT_ROOT="$_saved_root" + fi +fi + +# Configuracion global del usuario (GITLAB_URL, DEPLOY_SERVER, etc.) +# Variables de entorno ya definidas tienen prioridad sobre el archivo. +if [[ -f "${HOME}/.bdt/config" ]]; then + while IFS='=' read -r _k _v; do + [[ "$_k" =~ ^[A-Z_]+$ ]] || continue + [[ -n "${!_k:-}" ]] || export "$_k"="$_v" + done < "${HOME}/.bdt/config" || true +fi +INFRA_DIR="${BDT_ROOT}/infrastructure" +COMPOSE_FILE="${INFRA_DIR}/docker-compose.yml" +FRONTEND_DIR="${BDT_ROOT}/frontend/bdt-internet-banking" +GATEWAY_DIR="${BDT_ROOT}/backend/gateway" +CHANNELS_DIR="${BDT_ROOT}/backend/channels" +CANALES_DIR="${BDT_ROOT}/backend/canales" +SERVICEBUS_DIR="${BDT_ROOT}/backend/service-bus" +APIBANK_DIR="${BDT_ROOT}/backend/apiBank" +DATABASE_DIR="${BDT_ROOT}/database" +LOG_DIR="${BDT_ROOT}/logs" +mkdir -p "${LOG_DIR}" + +# GitLab +GITLAB_URL="${GITLAB_URL:-http://gitlab.bicentenariobu.com.ve}" +GITLAB_GROUP="${GITLAB_GROUP:-dcgeeks}" + +# Lista de servicios +ALL_SERVICES="frontend gateway service-bus canales-admin-web canales-worker apibank infrastructure database" +APP_SERVICES="frontend gateway service-bus canales-admin-web canales-worker" + +# Funciones de lookup (compatible bash 3.2 — sin declare -A) +repo_url() { + case "$1" in + frontend) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-frontend.git" ;; + gateway) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-gateway.git" ;; + service-bus) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-service-bus.git" ;; + canales-admin-web) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt_canales.git" ;; + canales-worker) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt_canales.git" ;; + apibank) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-apibank.git" ;; + infrastructure) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-infrastructure.git" ;; + database) echo "${GITLAB_URL}/${GITLAB_GROUP}/bdt-database.git" ;; + esac +} + +repo_dir() { + case "$1" in + frontend) echo "${FRONTEND_DIR}" ;; + gateway) echo "${GATEWAY_DIR}" ;; + service-bus) echo "${SERVICEBUS_DIR}" ;; + canales-admin-web) echo "${CANALES_DIR}" ;; + canales-worker) echo "${CANALES_DIR}" ;; + apibank) echo "${APIBANK_DIR}" ;; + infrastructure) echo "${INFRA_DIR}" ;; + database) echo "${DATABASE_DIR}" ;; + esac +} + +repo_branch() { + case "$1" in + frontend) echo "develop" ;; + gateway) echo "develop" ;; + service-bus) echo "develop" ;; + canales-admin-web) echo "master" ;; + canales-worker) echo "master" ;; + apibank) echo "desarrollo" ;; + infrastructure) echo "develop" ;; + database) echo "main" ;; + esac +} + +# Remote que apunta al GitLab del banco. El nombre varía por repo +# (origin en canales, dcgeeks en gateway/frontend/infra). Vacío si no hay. +_bank_remote() { + local dir="$1" host + host="$(echo "$GITLAB_URL" | sed 's|^[a-z]*://||')" + git -C "$dir" remote -v 2>/dev/null | awk -v h="$host" '$2 ~ h {print $1; exit}' +} + +svc_port() { + case "$1" in + postgres) echo 55432 ;; redis) echo 56379 ;; redpanda) echo 59092 ;; + keycloak) echo 58181 ;; mailpit) echo 58025 ;; prometheus) echo 59090 ;; + grafana) echo 53000 ;; frontend) echo 59002 ;; gateway) echo 58050 ;; + canales-admin-web) echo 58180 ;; canales-worker) echo 59091 ;; + service-bus) echo 58060 ;; adminer) echo 55050 ;; + redis-insight) echo 58081 ;; redpanda-console) echo 58080 ;; + esac +} + +svc_health_url() { + case "$1" in + gateway) echo "http://localhost:58050/actuator/health" ;; + service-bus) echo "http://localhost:58060/actuator/health" ;; + canales-admin-web) echo "http://localhost:58180/api/health" ;; + canales-worker) echo "http://localhost:59091/health" ;; + frontend) echo "http://localhost:59002" ;; + keycloak) echo "http://localhost:58181" ;; + prometheus) echo "http://localhost:59090" ;; + grafana) echo "http://localhost:53000" ;; + mailpit) echo "http://localhost:58025" ;; + esac +} + +docker_name() { + case "$1" in + postgres) echo "bdt-postgres" ;; redis) echo "bdt-redis" ;; redpanda) echo "bdt-redpanda" ;; + keycloak) echo "bdt-keycloak" ;; mailpit) echo "bdt-mailpit" ;; prometheus) echo "bdt-prometheus" ;; + grafana) echo "bdt-grafana" ;; frontend) echo "bdt-frontend" ;; gateway) echo "bdt-gateway" ;; + canales-admin-web) echo "bdt-canales-admin-web" ;; canales-worker) echo "bdt-canales-worker" ;; + service-bus) echo "bdt-service-bus" ;; adminer) echo "bdt-adminer" ;; + redis-insight) echo "bdt-redis-insight" ;; redpanda-console) echo "bdt-redpanda-console" ;; + esac +} + +# Ambiente +ENV_FILE="${BDT_ROOT}/.bdt-env" +if [[ -f "$ENV_FILE" ]]; then + CURRENT_ENV=$(cat "$ENV_FILE") +else + CURRENT_ENV="desarrollo" + echo "$CURRENT_ENV" > "$ENV_FILE" +fi + +# Docker Compose +COMPOSE_CMD="" +if docker compose version &>/dev/null 2>&1; then + COMPOSE_CMD="docker compose" +elif command -v docker-compose &>/dev/null; then + COMPOSE_CMD="docker-compose" +else + COMPOSE_CMD="docker compose" +fi + +# Servicios (lookup via funciones svc_port, svc_health_url, docker_name arriba) + +#=============================================================================== +# COLORES +#=============================================================================== + +NC='\033[0m'; BOLD='\033[1m'; DIM='\033[2m' +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +BLUE='\033[0;34m'; CYAN='\033[0;36m'; WHITE='\033[1;37m' +GRAY='\033[38;5;245m' +O1='\033[38;5;202m'; O2='\033[38;5;208m'; O3='\033[38;5;214m' + +ok() { echo -e " ${GREEN}${BOLD}✓${NC} $1"; } +err() { echo -e " ${RED}${BOLD}✗${NC} $1"; } +warn() { echo -e " ${YELLOW}${BOLD}⚠${NC} $1"; } +info() { echo -e " ${O3}→${NC} $1"; } +step() { echo -e "\n ${O2}${BOLD}[$1]${NC} ${WHITE}$2${NC}"; echo -e " ${GRAY}$(printf '%.0s─' {1..60})${NC}"; } + +env_label() { + case "$CURRENT_ENV" in + desarrollo) echo "DEV" ;; + calidad) echo "QA" ;; + demo) echo "DEMO" ;; + produccion) echo "PROD" ;; + *) echo "$CURRENT_ENV" ;; + esac +} + +banner() { + echo "" + echo -e "${O1} ██████╗ ██████╗ ████████╗${NC}" + echo -e "${O2} ██╔══██╗██╔══██╗╚══██╔══╝${NC} ${WHITE}${BOLD}BDT Platform v${SCRIPT_VERSION}${NC}" + echo -e "${O3} ██████╔╝██║ ██║ ██║ ${NC} Ambiente: ${CYAN}${BOLD}$(env_label)${NC} (${CURRENT_ENV})" + echo -e "${O2} ██╔══██╗██║ ██║ ██║ ${NC} Servidor: ${WHITE}$(hostname)${NC}" + echo -e "${O1} ██████╔╝██████╔╝ ██║ ${NC} Fecha: ${WHITE}$(date '+%Y-%m-%d %H:%M')${NC}" + echo -e "${O1} ╚═════╝ ╚═════╝ ╚═╝ ${NC}" + echo "" +} + +#=============================================================================== +# COMANDO: clone - Clonar todos los repositorios +#=============================================================================== + +cmd_clone() { + banner + step "CLONE" "Clonando repositorios desde GitLab (${GITLAB_GROUP})" + local branch="${1:-}" + + for svc in $ALL_SERVICES; do + local dir="$(repo_dir "$svc")" + local repo="$(repo_url "$svc")" + local br="${branch:-$(repo_branch "$svc")}" + + if [[ -d "$dir/.git" ]]; then + local remote + remote="$(_bank_remote "$dir")"; remote="${remote:-origin}" + info "${svc}: ya existe en ${dir}, actualizando (remote: ${remote})..." + cd "$dir" + git fetch "$remote" --prune 2>/dev/null + git checkout "$br" 2>/dev/null || git checkout -b "$br" "${remote}/${br}" 2>/dev/null || true + git pull "$remote" "$br" 2>/dev/null && ok "${svc}: actualizado (${br})" || warn "${svc}: pull falló" + cd "$BDT_ROOT" + else + info "${svc}: clonando..." + local parent_dir="$(dirname "$dir")" + mkdir -p "$parent_dir" + git clone -b "$br" "$repo" "$dir" 2>/dev/null && \ + ok "${svc}: clonado (${br})" || err "${svc}: error al clonar" + fi + done +} + +#=============================================================================== +# COMANDO: pull - Actualizar todos los repositorios +#=============================================================================== + +cmd_pull() { + banner + step "PULL" "Actualizando todos los repositorios" + local branch="${1:-}" + + for svc in $ALL_SERVICES; do + local dir="$(repo_dir "$svc")" + local br="${branch:-$(repo_branch "$svc")}" + + if [[ -d "$dir/.git" ]]; then + cd "$dir" + local remote + remote="$(_bank_remote "$dir")"; remote="${remote:-origin}" + local current_br=$(git branch --show-current 2>/dev/null) + if [[ -n "$branch" && "$current_br" != "$br" ]]; then + git fetch "$remote" --prune 2>/dev/null || true + git checkout "$br" 2>/dev/null || git checkout -b "$br" "${remote}/${br}" 2>/dev/null || true + current_br="$br" + fi + git pull "$remote" "${current_br:-$br}" 2>/dev/null && ok "${svc}: actualizado (${current_br:-$br})" || warn "${svc}: sin cambios o error" + cd "$BDT_ROOT" + else + warn "${svc}: no existe. Usa './bdt.sh clone' primero" + fi + done +} + +#=============================================================================== +# COMANDO: install - Instalar dependencias y compilar todo +#=============================================================================== + +cmd_install() { + banner + step "INSTALL" "Instalando dependencias y compilando todo" + local errors=0 + + # Prerequisitos + info "Verificando prerequisitos..." + command -v docker &>/dev/null && ok "Docker: $(docker --version 2>/dev/null | head -c 50)" || { err "Docker no instalado"; errors=$((errors+1)); } + command -v java &>/dev/null && ok "Java: $(java -version 2>&1 | head -1)" || { err "Java no instalado"; errors=$((errors+1)); } + command -v node &>/dev/null && ok "Node.js: $(node --version)" || { err "Node.js no instalado"; errors=$((errors+1)); } + [[ $errors -gt 0 ]] && { err "Prerequisitos faltantes. Abortando."; return 1; } + echo "" + + # Frontend + step "NPM" "Instalando Frontend (Next.js)" + if [[ -d "$FRONTEND_DIR" ]]; then + cd "$FRONTEND_DIR" + npm install --legacy-peer-deps > "${LOG_DIR}/frontend-install.log" 2>&1 && \ + ok "Frontend: dependencias instaladas" || { err "Frontend: npm install falló"; errors=$((errors+1)); } + npm run build > "${LOG_DIR}/frontend-build.log" 2>&1 && \ + ok "Frontend: build exitoso" || { err "Frontend: build falló (ver ${LOG_DIR}/frontend-build.log)"; errors=$((errors+1)); } + cd "$BDT_ROOT" + fi + + # Backends Gradle (canales compila dentro del Docker build, no acá) + for svc in gateway service-bus; do + local dir="$(repo_dir "$svc")" + step "GRADLE" "Compilando ${svc}" + if [[ -d "$dir" && -f "${dir}/build.gradle" ]]; then + cd "$dir" + ./gradlew clean build -x test > "${LOG_DIR}/${svc}-build.log" 2>&1 && \ + ok "${svc}: compilado" || { err "${svc}: build falló (ver ${LOG_DIR}/${svc}-build.log)"; errors=$((errors+1)); } + cd "$BDT_ROOT" + else + warn "${svc}: sin build.gradle" + fi + done + + echo "" + [[ $errors -eq 0 ]] && ok "${GREEN}${BOLD}Instalación completa sin errores${NC}" || \ + warn "${YELLOW}Instalación con ${errors} error(es). Revisa logs en ${LOG_DIR}/${NC}" +} + +#=============================================================================== +# COMANDO: build - Construir imágenes Docker +#=============================================================================== + +# Build local de bdt-:latest con el Dockerfile correcto +# (canales-* lo tienen en apps//Dockerfile, contexto = raíz del monorepo canales) +_docker_build_local() { + local svc="$1" + local dir dockerfile dockerfile_path + dir="$(repo_dir "$svc")" + [[ -z "$dir" ]] && { err "Servicio desconocido: ${svc}"; return 1; } + dockerfile="$(_image_dockerfile "$svc")" + dockerfile_path="${dir}/${dockerfile:-Dockerfile}" + [[ ! -f "$dockerfile_path" ]] && { err "Sin Dockerfile: ${svc} (${dockerfile_path})"; return 1; } + + local args=(-t "bdt-${svc}:latest") + [[ -n "$dockerfile" ]] && args+=(-f "$dockerfile_path") + args+=("$dir") + info "Construyendo bdt-${svc}:latest..." + if docker build "${args[@]}" > "${LOG_DIR}/${svc}-docker-build.log" 2>&1; then + ok "bdt-${svc}: imagen construida" + else + err "bdt-${svc}: build falló (ver ${LOG_DIR}/${svc}-docker-build.log)" + tail -15 "${LOG_DIR}/${svc}-docker-build.log" | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + return 1 + fi +} + +cmd_build() { + banner + local svc="${1:-all}" + step "BUILD" "Construyendo imágenes Docker" + + if [[ "$svc" == "all" ]]; then + for s in $APP_SERVICES; do + _docker_build_local "$s" || true + done + else + _docker_build_local "$svc" + fi +} + +#=============================================================================== +# COMANDO: up - Levantar servicios +#=============================================================================== + +cmd_up() { + banner + local target="${1:-all}" + cd "$INFRA_DIR" + + case "$target" in + infra) + step "UP" "Levantando infraestructura" + $COMPOSE_CMD up -d postgres redis redpanda keycloak mailpit prometheus grafana 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "Infraestructura levantada" + ;; + apps) + step "UP" "Levantando aplicaciones" + $COMPOSE_CMD --profile apps up -d 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "Aplicaciones levantadas" + ;; + tools) + step "UP" "Levantando herramientas" + $COMPOSE_CMD --profile tools up -d 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "Herramientas levantadas" + ;; + all) + step "UP" "Levantando TODOS los servicios" + $COMPOSE_CMD --profile apps --profile tools up -d 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "Todos los servicios levantados" + ;; + *) + step "UP" "Levantando servicio: ${target}" + $COMPOSE_CMD --profile apps --profile tools up -d "$target" 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "${target} levantado" + ;; + esac + + cd "$BDT_ROOT" + echo "" + cmd_status +} + +#=============================================================================== +# COMANDO: down - Detener servicios +#=============================================================================== + +cmd_down() { + banner + local target="${1:-all}" + cd "$INFRA_DIR" + + case "$target" in + all) + step "DOWN" "Deteniendo TODOS los servicios" + $COMPOSE_CMD --profile apps --profile tools down 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + pkill -f "java.*bootRun" 2>/dev/null || true + pkill -f "next dev" 2>/dev/null || true + ok "Todos los servicios detenidos" + ;; + *) + step "DOWN" "Deteniendo: ${target}" + $COMPOSE_CMD --profile apps --profile tools stop "$target" 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "${target} detenido" + ;; + esac + cd "$BDT_ROOT" +} + +#=============================================================================== +# COMANDO: restart - Reiniciar servicios +#=============================================================================== + +cmd_restart() { + local target="${1:-all}" + if [[ "$target" == "all" ]]; then + cmd_down all + sleep 2 + cmd_up all + else + banner + step "RESTART" "Reiniciando: ${target}" + cd "$INFRA_DIR" + $COMPOSE_CMD --profile apps --profile tools restart "$target" 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "${target} reiniciado" + cd "$BDT_ROOT" + fi +} + +#=============================================================================== +# COMANDO: status - Estado de todos los servicios +#=============================================================================== + +cmd_status() { + banner + step "STATUS" "Estado de servicios" + + echo -e "\n ${O3}${BOLD}Endpoints HTTP:${NC}" + for svc in gateway service-bus canales-admin-web canales-worker frontend keycloak prometheus grafana mailpit; do + local url="$(svc_health_url "$svc")" + local code + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url" 2>/dev/null || echo "000") + if [[ "$code" =~ ^(200|302|401)$ ]]; then + echo -e " ${GREEN}●${NC} ${WHITE}${svc}${NC}$(printf '%*s' $((18 - ${#svc})) '')${GREEN}HTTP ${code}${NC} ${DIM}${url}${NC}" + else + echo -e " ${RED}●${NC} ${GRAY}${svc}${NC}$(printf '%*s' $((18 - ${#svc})) '')${RED}HTTP ${code}${NC} ${DIM}detenido${NC}" + fi + done + + echo -e "\n ${O3}${BOLD}Contenedores Docker:${NC}" + for svc in postgres redis redpanda keycloak gateway canales-admin-web canales-worker service-bus frontend mailpit prometheus grafana; do + local cname="$(docker_name "$svc")" + local st + st=$(docker inspect --format='{{.State.Status}}' "$cname" 2>/dev/null || echo "not found") + local hp + hp=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}-{{end}}' "$cname" 2>/dev/null || echo "-") + + if [[ "$st" == "running" ]]; then + local color="$GREEN" + [[ "$hp" != "healthy" && "$hp" != "-" ]] && color="$YELLOW" + echo -e " ${color}●${NC} ${WHITE}${cname}${NC}$(printf '%*s' $((22 - ${#cname})) '')${color}${st}${NC} ${DIM}(${hp})${NC}" + else + echo -e " ${RED}●${NC} ${GRAY}${cname}${NC}$(printf '%*s' $((22 - ${#cname})) '')${RED}${st}${NC}" + fi + done + + echo -e "\n ${O3}${BOLD}Recursos Docker:${NC}" + local imgs=$(docker images -q 2>/dev/null | wc -l | tr -d ' ') + local ctrs=$(docker ps -q 2>/dev/null | wc -l | tr -d ' ') + echo -e " Contenedores activos: ${WHITE}${ctrs}${NC} | Imágenes: ${WHITE}${imgs}${NC}" +} + +#=============================================================================== +# COMANDO: logs - Ver logs de servicios +#=============================================================================== + +cmd_logs() { + local svc="${1:-}" + local lines="${2:-100}" + + if [[ -z "$svc" ]]; then + banner + step "LOGS" "Servicios disponibles" + echo "" + echo -e " ${O3}${BOLD}Docker (tiempo real con -f):${NC}" + echo -e " ${WHITE}gateway${NC} ${WHITE}canales-admin-web${NC} ${WHITE}canales-worker${NC} ${WHITE}service-bus${NC}" + echo -e " ${WHITE}frontend${NC} ${WHITE}postgres${NC} ${WHITE}redis${NC} ${WHITE}redpanda${NC}" + echo -e " ${WHITE}keycloak${NC} ${WHITE}prometheus${NC} ${WHITE}grafana${NC} ${WHITE}mailpit${NC}" + echo "" + echo -e " ${O3}${BOLD}Uso:${NC}" + echo -e " ${GRAY}./bdt.sh logs gateway${NC} Últimas 100 líneas" + echo -e " ${GRAY}./bdt.sh logs gateway 500${NC} Últimas 500 líneas" + echo -e " ${GRAY}./bdt.sh logs gateway -f${NC} Seguir en tiempo real" + echo -e " ${GRAY}./bdt.sh logs all${NC} Todos los servicios combinados" + echo "" + return + fi + + if [[ "$svc" == "all" ]]; then + cd "$INFRA_DIR" + echo -e " ${O3}Mostrando logs combinados (Ctrl+C para salir)...${NC}" + $COMPOSE_CMD --profile apps --profile tools logs -f --tail="$lines" 2>&1 + cd "$BDT_ROOT" + return + fi + + local cname="$(docker_name "$svc")" + if [[ -z "$cname" ]]; then + err "Servicio desconocido: ${svc}" + return 1 + fi + + if [[ "$lines" == "-f" ]]; then + echo -e " ${O3}Siguiendo logs de ${svc} (Ctrl+C para salir)...${NC}" + docker logs -f --tail=100 "$cname" 2>&1 + else + echo -e " ${O3}Últimas ${lines} líneas de ${svc}:${NC}" + echo "" + docker logs --tail="$lines" "$cname" 2>&1 + fi +} + +#=============================================================================== +# COMANDO: health - Health check completo +#=============================================================================== + +cmd_health() { + banner + step "HEALTH" "Verificación de salud del sistema" + local total=0 healthy=0 + + check() { + local name=$1 url=$2 + total=$((total + 1)) + local code + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo "000") + if [[ "$code" =~ ^(200|302|401)$ ]]; then + healthy=$((healthy + 1)) + echo -e " ${GREEN}✓${NC} ${WHITE}${name}${NC}$(printf '%*s' $((22 - ${#name})) '')HTTP ${GREEN}${code}${NC}" + else + echo -e " ${RED}✗${NC} ${GRAY}${name}${NC}$(printf '%*s' $((22 - ${#name})) '')HTTP ${RED}${code}${NC}" + fi + } + + echo -e "\n ${O3}${BOLD}Aplicaciones:${NC}" + check "Gateway API" "http://localhost:58050/actuator/health" + check "Channels Admin" "http://localhost:58180" + check "Service-Bus" "http://localhost:58060/actuator/health" + check "Frontend" "http://localhost:59002" + + echo -e "\n ${O3}${BOLD}Infraestructura:${NC}" + check "Keycloak" "http://localhost:58181" + check "Grafana" "http://localhost:53000" + check "Prometheus" "http://localhost:59090" + check "Mailpit" "http://localhost:58025" + + echo -e "\n ${O3}${BOLD}Base de Datos:${NC}" + if docker exec bdt-postgres pg_isready -U postgres -d bdt_ebanking_db &>/dev/null; then + total=$((total + 1)); healthy=$((healthy + 1)) + echo -e " ${GREEN}✓${NC} ${WHITE}PostgreSQL${NC} ${GREEN}Aceptando conexiones${NC}" + else + total=$((total + 1)) + echo -e " ${RED}✗${NC} ${GRAY}PostgreSQL${NC} ${RED}No disponible${NC}" + fi + + echo -e "\n ${O3}${BOLD}Cache:${NC}" + if docker exec bdt-redis redis-cli ping &>/dev/null; then + total=$((total + 1)); healthy=$((healthy + 1)) + echo -e " ${GREEN}✓${NC} ${WHITE}Redis${NC} ${GREEN}PONG${NC}" + else + total=$((total + 1)) + echo -e " ${RED}✗${NC} ${GRAY}Redis${NC} ${RED}No disponible${NC}" + fi + + echo "" + local pct=0 + [[ $total -gt 0 ]] && pct=$((healthy * 100 / total)) + if [[ $pct -eq 100 ]]; then + echo -e " ${GREEN}${BOLD}Sistema: ${healthy}/${total} (${pct}%) — OPERATIVO${NC}" + elif [[ $pct -ge 60 ]]; then + echo -e " ${YELLOW}${BOLD}Sistema: ${healthy}/${total} (${pct}%) — PARCIAL${NC}" + else + echo -e " ${RED}${BOLD}Sistema: ${healthy}/${total} (${pct}%) — CON PROBLEMAS${NC}" + fi +} + +#=============================================================================== +# COMANDO: verify-db - Verificar base de datos +#=============================================================================== + +cmd_verify_db() { + banner + step "DB" "Verificación de Base de Datos" + + if ! docker exec bdt-postgres pg_isready -U postgres &>/dev/null; then + err "PostgreSQL no accesible" + return 1 + fi + ok "Conexión PostgreSQL OK" + + echo -e "\n ${O3}${BOLD}Bases de datos:${NC}" + docker exec bdt-postgres psql -U postgres -t -c \ + "SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datistemplate=false ORDER BY datname;" 2>/dev/null | \ + while IFS='|' read -r db size; do + db=$(echo "$db" | xargs); size=$(echo "$size" | xargs) + [[ -n "$db" ]] && echo -e " ${GREEN}●${NC} ${WHITE}${db}${NC}$(printf '%*s' $((25 - ${#db})) '')${GRAY}${size}${NC}" + done + + echo -e "\n ${O3}${BOLD}Tablas en bdt_ebanking_db:${NC}" + local count + count=$(docker exec bdt-postgres psql -U postgres -d bdt_ebanking_db -t -c \ + "SELECT count(*) FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema');" 2>/dev/null | xargs) + echo -e " Total: ${WHITE}${count:-0}${NC} tablas" + + echo -e "\n ${O3}${BOLD}Conexiones activas:${NC}" + local conns + conns=$(docker exec bdt-postgres psql -U postgres -t -c "SELECT count(*) FROM pg_stat_activity WHERE state='active';" 2>/dev/null | xargs) + echo -e " Activas: ${WHITE}${conns:-0}${NC}" +} + +#=============================================================================== +# COMANDO: verify-api - Verificar endpoints API +#=============================================================================== + +cmd_verify_api() { + banner + step "API" "Verificación de endpoints API" + local total=0 ok_count=0 + + test_ep() { + local name=$1 method=$2 url=$3 expected=$4 + total=$((total + 1)) + local code + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -X "$method" "$url" 2>/dev/null || echo "000") + local match=false + for c in $expected; do [[ "$code" == "$c" ]] && match=true; done + if [[ "$match" == true ]]; then + ok_count=$((ok_count + 1)) + echo -e " ${GREEN}✓${NC} ${WHITE}${name}${NC}$(printf '%*s' $((30 - ${#name})) '')${GREEN}${code}${NC}" + else + echo -e " ${RED}✗${NC} ${GRAY}${name}${NC}$(printf '%*s' $((30 - ${#name})) '')${RED}${code}${NC}" + fi + } + + echo -e "\n ${O3}${BOLD}Gateway (:58050):${NC}" + test_ep "Health" GET "http://localhost:58050/actuator/health" "200" + test_ep "Login" POST "http://localhost:58050/api/login" "400 401 200 415" + test_ep "KS2 Cuentas" GET "http://localhost:58050/api/v1/ks2/consulta/cuentas/test" "401 403 200 404" + + echo -e "\n ${O3}${BOLD}Service-Bus (:58060):${NC}" + test_ep "Health" GET "http://localhost:58060/actuator/health" "200" + test_ep "Endpoints List" GET "http://localhost:58060/api/v1/endpoints" "200 401" + + echo -e "\n ${O3}${BOLD}Channels (:58180):${NC}" + test_ep "Home" GET "http://localhost:58180" "200 302" + test_ep "Bank Config" GET "http://localhost:58180/api/v1/bank-config/health" "200" + + echo -e "\n ${O3}${BOLD}Frontend (:59002):${NC}" + test_ep "Home" GET "http://localhost:59002" "200 302 307" + test_ep "Login Page" GET "http://localhost:59002/login" "200 302 307" + + echo "" + local pct=0 + [[ $total -gt 0 ]] && pct=$((ok_count * 100 / total)) + echo -e " APIs: ${WHITE}${ok_count}/${total}${NC} (${pct}%)" +} + +#=============================================================================== +# COMANDO: env - Cambiar ambiente +#=============================================================================== + +cmd_env() { + local new_env="${1:-}" + if [[ -z "$new_env" ]]; then + banner + step "ENV" "Ambiente actual: ${CURRENT_ENV} ($(env_label))" + echo "" + echo -e " ${GREEN}desarrollo${NC} — Desarrollo local, debug activo" + echo -e " ${YELLOW}calidad${NC} — QA, tests de integración" + echo -e " ${BLUE}demo${NC} — Presentaciones, datos demo" + echo -e " ${RED}produccion${NC} — Servidor de producción" + echo "" + echo -e " Uso: ${GRAY}./bdt.sh env desarrollo${NC}" + return + fi + + case "$new_env" in + desarrollo|calidad|demo|produccion) + CURRENT_ENV="$new_env" + echo "$CURRENT_ENV" > "$ENV_FILE" + ok "Ambiente cambiado a: ${BOLD}${CURRENT_ENV} ($(env_label))${NC}" + ;; + *) + err "Ambiente no válido: ${new_env}. Opciones: desarrollo, calidad, demo, produccion" + ;; + esac +} + +#=============================================================================== +# COMANDO: update - Actualizar, compilar y reiniciar un servicio +#=============================================================================== + +cmd_update() { + local svc="${1:-all}" + banner + step "UPDATE" "Actualizar servicio: ${svc}" + + if [[ "$svc" == "all" ]]; then + cmd_pull + cmd_install + cmd_build all + cmd_restart all + else + local dir="$(repo_dir "$svc")" + if [[ -z "$dir" ]]; then + err "Servicio desconocido: ${svc}" + return 1 + fi + + # Pull (remote del banco, detectado por URL) + info "Actualizando código..." + local remote + remote="$(_bank_remote "$dir")"; remote="${remote:-origin}" + cd "$dir" && git pull "$remote" "$(git branch --show-current)" 2>/dev/null && \ + ok "Código actualizado (${remote})" || warn "Sin cambios" + cd "$BDT_ROOT" + + # Build previo solo para Java: el Dockerfile espera el JAR en build/libs/ + # (frontend y canales compilan dentro del Docker build multi-stage) + if [[ -f "${dir}/build.gradle" ]]; then + info "Compilando (Gradle bootJar)..." + cd "$dir" && ./gradlew clean bootJar -x test > "${LOG_DIR}/${svc}-update-build.log" 2>&1 && \ + ok "Compilado" || { err "Build falló (ver ${LOG_DIR}/${svc}-update-build.log)"; cd "$BDT_ROOT"; return 1; } + cd "$BDT_ROOT" + fi + + # Docker rebuild + restart + if _docker_build_local "$svc"; then + info "Reiniciando contenedor..." + cd "$INFRA_DIR" + $COMPOSE_CMD --profile apps up -d --force-recreate "$svc" 2>/dev/null && \ + ok "${svc} reiniciado" || warn "Reinicio manual necesario" + cd "$BDT_ROOT" + fi + fi +} + +#=============================================================================== +# COMANDO: clean - Limpiar Docker +#=============================================================================== + +cmd_clean() { + banner + step "CLEAN" "Limpieza Docker" + + local stopped=$(docker ps -aq --filter "status=exited" 2>/dev/null | wc -l | tr -d ' ') + local dangling=$(docker images -f "dangling=true" -q 2>/dev/null | wc -l | tr -d ' ') + echo -e " Contenedores detenidos: ${WHITE}${stopped}${NC}" + echo -e " Imágenes sin tag: ${WHITE}${dangling}${NC}" + echo "" + + docker container prune -f 2>/dev/null && ok "Contenedores limpiados" + docker image prune -f 2>/dev/null && ok "Imágenes limpiadas" + docker network prune -f 2>/dev/null && ok "Redes limpiadas" +} + +#=============================================================================== +# COMANDO: git-auth - Configurar credenciales Git permanentes +#=============================================================================== + +cmd_git_auth() { + banner + step "GIT-AUTH" "Configurar credenciales GitLab" + echo "" + + local git_user="${1:-}" + local git_pass="${2:-}" + + if [[ -z "$git_user" ]]; then + echo -ne " Usuario GitLab: " + read -r git_user + fi + + if [[ -z "$git_pass" ]]; then + echo -ne " Contraseña GitLab: " + read -rs git_pass + echo "" + fi + + if [[ -z "$git_user" || -z "$git_pass" ]]; then + err "Usuario y contraseña son requeridos" + return 1 + fi + + # Método 1: Git credential store (almacena en archivo) + git config --global credential.helper store + ok "Credential helper configurado: store" + + # Método 2: Crear entrada en el credential store + local cred_file="${HOME}/.git-credentials" + local encoded_pass + encoded_pass=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${git_pass}', safe=''))" 2>/dev/null || echo "$git_pass") + local cred_line="http://${git_user}:${encoded_pass}@gitlab.bicentenariobu.com.ve" + + # Verificar si ya existe + if grep -q "gitlab.bicentenariobu.com.ve" "$cred_file" 2>/dev/null; then + # Reemplazar credencial existente + sed -i.bak "/gitlab.bicentenariobu.com.ve/d" "$cred_file" 2>/dev/null || true + fi + + echo "$cred_line" >> "$cred_file" + chmod 600 "$cred_file" + ok "Credenciales guardadas en ${cred_file}" + + # Método 3: Actualizar URLs de los remotes para usar el usuario (sin password en URL) + info "Actualizando remotes de los repositorios..." + for svc in $ALL_SERVICES; do + local dir="$(repo_dir "$svc")" + if [[ -d "$dir/.git" ]]; then + cd "$dir" + # Verificar si tiene remote dcgeeks + if git remote get-url dcgeeks &>/dev/null; then + local current_url=$(git remote get-url dcgeeks) + # Limpiar credenciales de la URL si las tiene + local clean_url=$(echo "$current_url" | sed 's|http://[^@]*@|http://|') + git remote set-url dcgeeks "$clean_url" 2>/dev/null + fi + # Verificar si tiene remote origin + if git remote get-url origin &>/dev/null; then + local current_url=$(git remote get-url origin) + local clean_url=$(echo "$current_url" | sed 's|http://[^@]*@|http://|') + git remote set-url origin "$clean_url" 2>/dev/null + fi + cd "$BDT_ROOT" + fi + done + ok "Remotes actualizados" + + # Test + echo "" + info "Verificando acceso..." + local test_dir="$(repo_dir frontend)" + if [[ -d "$test_dir/.git" ]]; then + cd "$test_dir" + if GIT_TERMINAL_PROMPT=0 git ls-remote --heads origin 2>/dev/null | head -1 > /dev/null; then + ok "Acceso a GitLab verificado correctamente" + else + if GIT_TERMINAL_PROMPT=0 git ls-remote --heads dcgeeks 2>/dev/null | head -1 > /dev/null; then + ok "Acceso a GitLab verificado (remote dcgeeks)" + else + warn "No se pudo verificar. Las credenciales se usarán en el próximo push/pull" + fi + fi + cd "$BDT_ROOT" + fi + + echo "" + ok "Git configurado. Los próximos git pull/push no pedirán contraseña." +} + +#=============================================================================== +# COMANDO: creds - Mostrar credenciales +#=============================================================================== + +cmd_creds() { + banner + step "CREDS" "Credenciales del sistema" + echo "" + echo -e " ${O3}${BOLD}PostgreSQL:${NC} localhost:${WHITE}55432${NC} user: ${WHITE}postgres${NC} pass: ${WHITE}postgres${NC}" + echo -e " ${O3}${BOLD}Redis:${NC} localhost:${WHITE}56379${NC} ${GRAY}sin password (dev)${NC}" + echo -e " ${O3}${BOLD}Keycloak:${NC} localhost:${WHITE}58181${NC} user: ${WHITE}admin${NC} pass: ${WHITE}admin${NC}" + echo -e " ${O3}${BOLD}Grafana:${NC} localhost:${WHITE}53000${NC} user: ${WHITE}admin${NC} pass: ${WHITE}admin${NC}" + echo -e " ${O3}${BOLD}Mailpit:${NC} localhost:${WHITE}58025${NC} ${GRAY}sin auth${NC}" + echo -e " ${O3}${BOLD}Adminer:${NC} localhost:${WHITE}55050${NC} ${GRAY}usar creds de PostgreSQL${NC}" + echo -e " ${O3}${BOLD}Redpanda Console:${NC} localhost:${WHITE}58080${NC} ${GRAY}sin auth${NC}" + echo -e " ${O3}${BOLD}Redis Insight:${NC} localhost:${WHITE}58081${NC} ${GRAY}sin auth${NC}" + echo "" + echo -e " ${O3}${BOLD}URLs DNS (si Nginx configurado):${NC}" + echo -e " Frontend: ${WHITE}https://ebanking.bdt.local${NC}" + echo -e " Gateway: ${WHITE}https://gateway.bdt.local${NC}" + echo -e " Channels: ${WHITE}https://channels.bdt.local${NC}" + echo -e " Keycloak: ${WHITE}https://keycloak.bdt.local${NC}" +} + +#=============================================================================== +# COMANDO: deploy - Pipeline completo: pull → build → docker → restart +#=============================================================================== + +cmd_deploy() { + banner + local env="${1:-$CURRENT_ENV}" + step "DEPLOY" "Deploy completo — Ambiente: ${env}" + local start_time=$(date +%s) + + echo "" + info "Paso 1/5: Actualizando repositorios..." + cmd_pull + echo "" + + info "Paso 2/5: Compilando aplicaciones..." + cmd_install + echo "" + + info "Paso 3/5: Construyendo imágenes Docker..." + cmd_build all + echo "" + + info "Paso 4/5: Levantando servicios..." + cmd_up all + echo "" + + info "Paso 5/5: Verificando salud..." + sleep 10 + cmd_health + + local elapsed=$(( $(date +%s) - start_time )) + echo "" + ok "Deploy completado en ${elapsed} segundos" +} + +#=============================================================================== +# COMANDO: setup - Autoinstalador global +#=============================================================================== + +cmd_setup() { + banner + step "SETUP" "Autoinstalador global de BDT Platform" + echo "" + + local install_dir="/usr/local/bin" + local config_dir="${HOME}/.bdt" + local completions_dir="" + + # Detectar shell del usuario + local user_shell="$(basename "$SHELL" 2>/dev/null || echo "bash")" + + # 1. Instalar comando global 'bdt' + echo -e " ${O3}${BOLD}[1/5] Instalando comando global 'bdt'...${NC}" + echo "" + + if [[ -w "$install_dir" ]]; then + # Sin sudo + ln -sf "${BDT_ROOT}/bdt.sh" "${install_dir}/bdt" 2>/dev/null + ok "Enlace creado: ${install_dir}/bdt -> bdt.sh" + else + info "Se necesita sudo para instalar en ${install_dir}" + sudo ln -sf "${BDT_ROOT}/bdt.sh" "${install_dir}/bdt" 2>/dev/null && \ + ok "Enlace creado: ${install_dir}/bdt -> bdt.sh" || { + # Fallback: instalar en ~/bin + install_dir="${HOME}/bin" + mkdir -p "$install_dir" + ln -sf "${BDT_ROOT}/bdt.sh" "${install_dir}/bdt" 2>/dev/null + ok "Enlace creado: ${install_dir}/bdt -> bdt.sh" + warn "Asegurate de que ${install_dir} este en tu PATH" + } + fi + echo "" + + # 2. Crear directorio de configuracion + echo -e " ${O3}${BOLD}[2/5] Configurando directorio ~/.bdt...${NC}" + mkdir -p "$config_dir" + + # Guardar ruta del proyecto + echo "${BDT_ROOT}" > "${config_dir}/project-root" + + # Crear archivo de configuracion si no existe + if [[ ! -f "${config_dir}/config" ]]; then + cat > "${config_dir}/config" << 'CONF' +# BDT Platform - Configuracion global +# Este archivo se carga automaticamente al ejecutar 'bdt' + +# GitLab +GITLAB_URL=http://gitlab.bicentenariobu.com.ve +GITLAB_GROUP=dcgeeks + +# Ambiente por defecto (desarrollo|calidad|demo|produccion) +DEFAULT_ENV=desarrollo + +# Servidor de deploy remoto (comando server-deploy) +# DEPLOY_SERVER=10.0.0.1 +# DEPLOY_USER=usuario.ssh + +# Logs +LOG_RETENTION_DAYS=30 +CONF + ok "Archivo de configuracion creado: ${config_dir}/config" + else + ok "Configuracion existente preservada: ${config_dir}/config" + fi + echo "" + + # 3. Autocompletado + echo -e " ${O3}${BOLD}[3/5] Instalando autocompletado...${NC}" + + local completion_script="${config_dir}/completion.bash" + cat > "$completion_script" << 'COMP' +# BDT Platform - Bash/Zsh completion +_bdt_completions() { + local cur="${COMP_WORDS[COMP_CWORD]}" + local prev="${COMP_WORDS[COMP_CWORD-1]}" + + local commands="clone pull install build up down restart status logs health verify-db verify-api deploy construir env update creds clean setup menu help image-login image-build image-push image-pull image-deploy image-build-amd64 server-deploy" + local services="frontend gateway service-bus canales-admin-web canales-worker apibank infrastructure database" + local up_targets="all infra apps tools frontend gateway canales-admin-web canales-worker service-bus postgres redis keycloak" + local envs="desarrollo calidad demo produccion" + local log_services="gateway canales-admin-web canales-worker service-bus frontend postgres redis redpanda keycloak prometheus grafana mailpit all" + + case "$prev" in + bdt|bdt.sh|./bdt.sh) + COMPREPLY=($(compgen -W "$commands" -- "$cur")) + ;; + up|start|down|stop|restart|update|build) + COMPREPLY=($(compgen -W "$up_targets" -- "$cur")) + ;; + logs|log) + COMPREPLY=($(compgen -W "$log_services" -- "$cur")) + ;; + env) + COMPREPLY=($(compgen -W "$envs" -- "$cur")) + ;; + clone|pull) + COMPREPLY=($(compgen -W "main develop calidad produccion" -- "$cur")) + ;; + esac +} +complete -F _bdt_completions bdt +complete -F _bdt_completions bdt.sh +complete -F _bdt_completions ./bdt.sh +COMP + ok "Script de completado generado: ${completion_script}" + + # Agregar source al perfil del shell + local profile_file="" + case "$user_shell" in + zsh) profile_file="${HOME}/.zshrc" ;; + bash) profile_file="${HOME}/.bashrc" ;; + *) profile_file="${HOME}/.profile" ;; + esac + + local source_line="[[ -f ~/.bdt/completion.bash ]] && source ~/.bdt/completion.bash" + if [[ -f "$profile_file" ]] && ! grep -q ".bdt/completion.bash" "$profile_file" 2>/dev/null; then + echo "" >> "$profile_file" + echo "# BDT Platform - autocompletado" >> "$profile_file" + echo "$source_line" >> "$profile_file" + ok "Autocompletado agregado a ${profile_file}" + else + ok "Autocompletado ya configurado en ${profile_file}" + fi + echo "" + + # 4. Alias utiles + echo -e " ${O3}${BOLD}[4/5] Configurando alias...${NC}" + + local alias_file="${config_dir}/aliases.bash" + cat > "$alias_file" << ALIASES +# BDT Platform - Alias utiles +alias bdt-up='bdt up all' +alias bdt-down='bdt down all' +alias bdt-status='bdt status' +alias bdt-logs='bdt logs' +alias bdt-deploy='bdt deploy' +alias bdt-health='bdt health' +alias bdt-menu='bdt menu' + +# Ir al directorio del proyecto +alias bdt-cd='cd ${BDT_ROOT}' + +# Logs rapidos +alias bdt-lg='bdt logs gateway -f' +alias bdt-lc='bdt logs canales-admin-web -f' +alias bdt-lw='bdt logs canales-worker -f' +alias bdt-ls='bdt logs service-bus -f' +alias bdt-lf='bdt logs frontend -f' +alias bdt-la='bdt logs all' +ALIASES + + local alias_source_line="[[ -f ~/.bdt/aliases.bash ]] && source ~/.bdt/aliases.bash" + if [[ -f "$profile_file" ]] && ! grep -q ".bdt/aliases.bash" "$profile_file" 2>/dev/null; then + echo "$alias_source_line" >> "$profile_file" + ok "Alias configurados en ${profile_file}" + else + ok "Alias ya configurados" + fi + echo "" + + # 5. Verificar instalacion + echo -e " ${O3}${BOLD}[5/5] Verificando instalacion...${NC}" + + if command -v bdt &>/dev/null || [[ -x "${install_dir}/bdt" ]]; then + ok "Comando 'bdt' disponible globalmente" + else + warn "El comando 'bdt' se instalara tras recargar el shell" + fi + + ok "Directorio de config: ${config_dir}" + ok "Proyecto raiz: ${BDT_ROOT}" + echo "" + + echo -e " ${GRAY}$(printf '%.0s─' {1..60})${NC}" + echo -e " ${GREEN}${BOLD}Instalacion completada.${NC}" + echo "" + echo -e " ${O3}${BOLD}Para activar ahora:${NC}" + echo -e " ${GRAY}source ${profile_file}${NC}" + echo "" + echo -e " ${O3}${BOLD}Despues puedes usar desde cualquier lugar:${NC}" + echo -e " ${WHITE}bdt${NC} Menu interactivo" + echo -e " ${WHITE}bdt deploy${NC} Deploy completo" + echo -e " ${WHITE}bdt status${NC} Estado del sistema" + echo -e " ${WHITE}bdt logs gw -f${NC} Seguir logs" + echo "" + echo -e " ${O3}${BOLD}Alias disponibles:${NC}" + echo -e " ${WHITE}bdt-up${NC} ${WHITE}bdt-down${NC} ${WHITE}bdt-status${NC} ${WHITE}bdt-deploy${NC}" + echo -e " ${WHITE}bdt-lg${NC} (logs gateway) ${WHITE}bdt-lc${NC} (logs channels)" + echo -e " ${WHITE}bdt-ls${NC} (logs service-bus) ${WHITE}bdt-lf${NC} (logs frontend)" + echo -e " ${WHITE}bdt-cd${NC} (ir al proyecto) ${WHITE}bdt-menu${NC} (menu interactivo)" + echo "" +} + +#=============================================================================== +# COMANDO: menu - Menu interactivo +#=============================================================================== + +# Ejecuta la opción elegida. Llamado con `|| true` para que un comando +# fallido no mate el menú (set -e). +_menu_dispatch() { + local choice="$1" + case "$choice" in + 0) cmd_construir ;; + 1) cmd_up all ;; + 2) cmd_up infra ;; + 3) cmd_up apps ;; + 4) cmd_down all ;; + 5) + echo -ne " Servicio a reiniciar (gateway/canales-admin-web/canales-worker/service-bus/frontend/postgres/redis/keycloak): " + read -r svc_name + [[ -n "$svc_name" ]] && cmd_restart "$svc_name" + ;; + 6) cmd_status ;; + 7) cmd_health ;; + 8) + echo -ne " Servicio (gateway/canales-admin-web/canales-worker/service-bus/frontend/postgres/redis/keycloak/all): " + read -r log_svc + echo -ne " Lineas (100) o -f para seguir: " + read -r log_lines + [[ -n "$log_svc" ]] && cmd_logs "$log_svc" "${log_lines:-100}" + ;; + 9) cmd_logs all 200 ;; + a) cmd_verify_db ;; + b) cmd_verify_api ;; + c) cmd_creds ;; + d) cmd_deploy ;; + e) cmd_install ;; + f) cmd_build all ;; + g) cmd_clone ;; + h) cmd_pull ;; + i) + echo -ne " Servicio a actualizar (gateway/canales-admin-web/canales-worker/service-bus/frontend/all): " + read -r upd_svc + [[ -n "$upd_svc" ]] && cmd_update "$upd_svc" + ;; + j) + echo -ne " Ambiente (desarrollo/calidad/demo/produccion): " + read -r new_env + [[ -n "$new_env" ]] && cmd_env "$new_env" + ;; + k) cmd_clean ;; + r) cmd_image_build_amd64 all ;; + t) cmd_server_deploy ;; + s) cmd_setup ;; + \?) cmd_help ;; + q|Q) + echo "" + echo -e " ${O3}Hasta luego!${NC}" + echo "" + exit 0 + ;; + *) warn "Opcion no valida" ;; + esac +} + +cmd_menu() { + while true; do + banner + echo -e " ${O1}${BOLD}0) CONSTRUIR SERVICIO${NC} ${GRAY}— asistente: git banco → build → docker → up${NC}" + echo "" + echo -e " ${O2}${BOLD}SERVICIOS${NC} ${O3}${BOLD}MONITOREO${NC} ${O1}${BOLD}VERIFICACION${NC}" + echo -e " ${GRAY}$(printf '%.0s─' {1..60})${NC}" + echo -e " ${WHITE}1${NC}) Levantar todo ${WHITE}6${NC}) Estado servicios ${WHITE}a${NC}) Verificar DB" + echo -e " ${WHITE}2${NC}) Solo infraestructura ${WHITE}7${NC}) Health check ${WHITE}b${NC}) Verificar APIs" + echo -e " ${WHITE}3${NC}) Solo aplicaciones ${WHITE}8${NC}) Ver logs ${WHITE}c${NC}) Credenciales" + echo -e " ${WHITE}4${NC}) Detener todo ${WHITE}9${NC}) Logs combinados" + echo -e " ${WHITE}5${NC}) Reiniciar servicio" + echo "" + echo -e " ${O2}${BOLD}DEPLOYMENT${NC} ${O3}${BOLD}REPOSITORIOS${NC} ${O1}${BOLD}SISTEMA${NC}" + echo -e " ${GRAY}$(printf '%.0s─' {1..60})${NC}" + echo -e " ${WHITE}d${NC}) Deploy completo ${WHITE}g${NC}) Clonar repos ${WHITE}s${NC}) Setup global" + echo -e " ${WHITE}e${NC}) Compilar todo ${WHITE}h${NC}) Actualizar repos ${WHITE}j${NC}) Cambiar ambiente" + echo -e " ${WHITE}f${NC}) Build Docker ${WHITE}i${NC}) Actualizar servicio ${WHITE}k${NC}) Limpiar Docker" + echo -e " ${WHITE}r${NC}) Build amd64 + push registry ${WHITE}t${NC}) Deploy en server (SSH)" + echo "" + echo -e " ${WHITE}?${NC}) Ayuda ${WHITE}q${NC}) Salir" + echo "" + echo -ne " ${O3}bdt [$(env_label)]${NC} ${O1}>${NC} " + read -r choice + + _menu_dispatch "$choice" || warn "El comando terminó con error (el menú sigue activo)" + + echo "" + echo -ne " ${GRAY}Presiona Enter para continuar...${NC}" + read -r + done +} + +#=============================================================================== +# COMANDOS: image-build / image-push / image-pull / image-deploy +# Build Docker local → push a Gitea Registry → pull en server banco. +# Credenciales en ~/.bdt/gitea-registry.env (chmod 600). +#=============================================================================== + +IMAGE_SERVICES=(gateway frontend service-bus canales-admin-web canales-worker) + +_image_dir() { + case "$1" in + gateway) echo "${GATEWAY_DIR}" ;; + frontend) echo "${FRONTEND_DIR}" ;; + service-bus) echo "${SERVICEBUS_DIR}" ;; + canales-admin-web) echo "${CANALES_DIR}" ;; + canales-worker) echo "${CANALES_DIR}" ;; + *) return 1 ;; + esac +} + +# Dockerfile relativo al build context (_image_dir). Vacío = usar default "/Dockerfile". +_image_dockerfile() { + case "$1" in + canales-admin-web) echo "apps/admin-web/Dockerfile" ;; + canales-worker) echo "apps/worker/Dockerfile" ;; + *) echo "" ;; + esac +} + +_image_load_creds() { + local env_file="${HOME}/.bdt/gitea-registry.env" + if [[ ! -f "$env_file" ]]; then + err "Falta ${env_file}" + echo " Ejecutá: ${GRAY}./bdt.sh image-login${NC}" + return 1 + fi + # shellcheck disable=SC1090 + set -a; source "$env_file"; set +a + : "${GITEA_REGISTRY_URL:?GITEA_REGISTRY_URL no definido}" + : "${GITEA_CI_USER:?GITEA_CI_USER no definido}" + : "${GITEA_CI_TOKEN:?GITEA_CI_TOKEN no definido}" + : "${GITEA_ORG:=bdt}" +} + +_image_tag() { + local svc="$1" kind="${2:-latest}" + echo "${GITEA_REGISTRY_URL}/${GITEA_ORG}/${svc}:${kind}" +} + +_image_sha() { + local dir="$1" + (cd "$dir" && git rev-parse --short HEAD 2>/dev/null) || echo "local" +} + +# Copia el JAR desde ~/.bdt/jars// → /build/libs/ +# El usuario debe dejar el JAR compilado en esa carpeta antes de llamar image-build. +_jar_copy() { + local svc="$1" dir="$2" + local jar_inbox="${HOME}/.bdt/jars/${svc}" + local jar_dest="${dir}/build/libs" + local jar_file + jar_file="$(ls "${jar_inbox}"/*.jar 2>/dev/null | head -1)" + if [[ -z "$jar_file" ]]; then + err "No hay JAR en ${jar_inbox}/" + warn "Copiá el .jar compilado → ${jar_inbox}/ y volvé a correr" + return 1 + fi + mkdir -p "$jar_dest" + rm -f "${jar_dest}"/*.jar + case "$svc" in + service-bus) + cp "$jar_file" "${jar_dest}/service-bus.jar" + ok "JAR → ${jar_dest}/service-bus.jar" + ;; + *) + cp "$jar_file" "${jar_dest}/" + ok "JAR → ${jar_dest}/$(basename "$jar_file")" + ;; + esac +} + +cmd_image_login() { + banner + step "IMAGE-LOGIN" "Docker login Gitea Registry" + _image_load_creds || return 1 + echo "$GITEA_CI_TOKEN" | docker login "$GITEA_REGISTRY_URL" -u "$GITEA_CI_USER" --password-stdin 2>&1 \ + | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "Login OK: ${GITEA_REGISTRY_URL}" +} + +cmd_image_build() { + banner + _image_load_creds || return 1 + local target="${1:-all}" + local services=() + if [[ "$target" == "all" ]]; then services=("${IMAGE_SERVICES[@]}"); else services=("$target"); fi + + for svc in "${services[@]}"; do + local dir dockerfile dockerfile_path + dir="$(_image_dir "$svc")" || { err "Servicio desconocido: $svc"; continue; } + dockerfile="$(_image_dockerfile "$svc")" + if [[ -n "$dockerfile" ]]; then + dockerfile_path="${dir}/${dockerfile}" + else + dockerfile_path="${dir}/Dockerfile" + fi + [[ ! -f "$dockerfile_path" ]] && { warn "Sin Dockerfile: ${svc} (${dockerfile_path})"; continue; } + + local sha tag_sha tag_latest + sha="$(_image_sha "$dir")" + tag_sha="$(_image_tag "$svc" "$sha")" + tag_latest="$(_image_tag "$svc" latest)" + + # JAR pre-compilado desde ~/.bdt/jars// (el usuario lo sube antes) + case "$svc" in + gateway|service-bus) + step "JAR-COPY" "${svc}" + _jar_copy "$svc" "$dir" || return 1 + ;; + esac + + step "IMAGE-BUILD" "${svc} @ ${sha}" + local build_args=(--no-cache -t "$tag_sha" -t "$tag_latest") + [[ -n "$dockerfile" ]] && build_args+=(-f "$dockerfile_path") + build_args+=("$dir") + echo -e " ${GRAY}docker build ${build_args[*]}${NC}" + if docker build "${build_args[@]}" 2>&1 | tail -20 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done; then + ok "${svc} → ${tag_sha}" + else + err "Build falló: ${svc}" + return 1 + fi + done +} + +cmd_image_push() { + banner + _image_load_creds || return 1 + local target="${1:-all}" + local services=() + if [[ "$target" == "all" ]]; then services=("${IMAGE_SERVICES[@]}"); else services=("$target"); fi + + # Ensure logged in + echo "$GITEA_CI_TOKEN" | docker login "$GITEA_REGISTRY_URL" -u "$GITEA_CI_USER" --password-stdin >/dev/null 2>&1 \ + || { err "Login fallido"; return 1; } + + for svc in "${services[@]}"; do + local dir sha tag_sha tag_latest + dir="$(_image_dir "$svc")" || continue + sha="$(_image_sha "$dir")" + tag_sha="$(_image_tag "$svc" "$sha")" + tag_latest="$(_image_tag "$svc" latest)" + + step "IMAGE-PUSH" "${svc} → ${GITEA_REGISTRY_URL}/${GITEA_ORG}/${svc}" + if ! docker image inspect "$tag_sha" >/dev/null 2>&1; then + warn "Imagen local ausente: ${tag_sha} — corré 'image-build ${svc}' primero" + continue + fi + docker push "$tag_sha" 2>&1 | tail -5 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + docker push "$tag_latest" 2>&1 | tail -5 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + ok "${svc} pushed: ${sha} + latest" + done +} + +cmd_image_pull() { + banner + _image_load_creds || return 1 + local target="${1:-all}" tag_kind="${2:-latest}" + local services=() + if [[ "$target" == "all" ]]; then services=("${IMAGE_SERVICES[@]}"); else services=("$target"); fi + + echo "$GITEA_CI_TOKEN" | docker login "$GITEA_REGISTRY_URL" -u "$GITEA_CI_USER" --password-stdin >/dev/null 2>&1 \ + || { err "Login fallido"; return 1; } + + for svc in "${services[@]}"; do + local tag + tag="$(_image_tag "$svc" "$tag_kind")" + step "IMAGE-PULL" "${svc} ← ${tag}" + if docker pull "$tag" 2>&1 | tail -5 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done; then + # Re-tag to local alias expected by docker-compose + docker tag "$tag" "bdt-${svc}:latest" + ok "${svc} pulled → bdt-${svc}:latest" + else + err "Pull falló: ${svc}" + fi + done +} + +cmd_image_deploy() { + banner + _image_load_creds || return 1 + local target="${1:-all}" tag_kind="${2:-latest}" + local services=() + if [[ "$target" == "all" ]]; then services=("${IMAGE_SERVICES[@]}"); else services=("$target"); fi + + step "IMAGE-DEPLOY" "Server-side deploy from ${GITEA_REGISTRY_URL}" + + # 1) Pull nuevos + cmd_image_pull "$target" "$tag_kind" + + # 2) Stop + rm contenedores viejos + cd "$INFRA_DIR" + for svc in "${services[@]}"; do + step "STOP" "bdt-${svc}" + $COMPOSE_CMD --profile apps stop "$svc" 2>/dev/null || true + $COMPOSE_CMD --profile apps rm -f "$svc" 2>/dev/null || true + done + + # 3) Remove imágenes locales dangling (del build anterior) + docker image prune -f >/dev/null 2>&1 || true + ok "Dangling images purged" + + # 4) Up con imagen nueva + step "UP" "Recreando servicios con imagen nueva" + $COMPOSE_CMD --profile apps up -d "${services[@]}" 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + + # 5) Health check + cd "$BDT_ROOT" + echo "" + cmd_health +} + +cmd_image_build_amd64() { + local script="${INFRA_DIR}/scripts/build-push-amd64.sh" + [[ ! -f "$script" ]] && { err "Script no encontrado: ${script}"; return 1; } + bash "$script" "$@" +} + +cmd_server_deploy() { + local script="${INFRA_DIR}/scripts/server-deploy.sh" + [[ ! -f "$script" ]] && { err "Script no encontrado: ${script}"; return 1; } + _image_load_creds || return 1 + + # Servidor destino: configurar en ~/.bdt/config (no se versiona) + local server="${DEPLOY_SERVER:-}" + local server_user="${DEPLOY_USER:-}" + if [[ -z "$server" || -z "$server_user" ]]; then + err "Falta configurar el servidor destino" + echo -e " Agregá en ${GRAY}~/.bdt/config${NC}:" + echo -e " ${GRAY}DEPLOY_SERVER=${NC}" + echo -e " ${GRAY}DEPLOY_USER=${NC}" + return 1 + fi + local remote_script="/tmp/bdt-server-deploy.sh" + + step "SCP" "Copiando script → ${server_user}@${server}" + scp "$script" "${server_user}@${server}:${remote_script}" || { err "SCP falló"; return 1; } + ok "Script copiado" + + step "SSH" "Ejecutando deploy en ${server}" + ssh "${server_user}@${server}" \ + "REGISTRY='${GITEA_REGISTRY_URL}' REGISTRY_USER='${GITEA_CI_USER}' REGISTRY_TOKEN='${GITEA_CI_TOKEN}' bash ${remote_script} $*" +} + +#=============================================================================== +# COMANDO: construir - Asistente: git banco → compilar → imagen Docker → up +#=============================================================================== + +svc_desc() { + case "$1" in + frontend) echo "Internet Banking (Next.js)" ;; + gateway) echo "API Gateway (Spring Boot)" ;; + service-bus) echo "Service Bus / ESB (Spring Boot)" ;; + canales-admin-web) echo "Canales Admin Web (pnpm)" ;; + canales-worker) echo "Canales Worker (pnpm)" ;; + esac +} + +cmd_construir() { + banner + step "ASISTENTE" "Construir y desplegar un servicio (git banco → build → docker → up)" + echo "" + + # 1. ¿Qué vas a construir? + local options=() i=1 s + for s in $APP_SERVICES; do + options+=("$s") + local cname st + cname="$(docker_name "$s")" + st="$(docker inspect --format='{{.State.Status}}' "$cname" 2>/dev/null | tr -d '[:space:]' || true)" + [[ -z "$st" ]] && st="sin contenedor" + echo -e " ${WHITE}${i}${NC}) ${WHITE}${s}${NC} — $(svc_desc "$s") ${GRAY}[rama: $(repo_branch "$s") | contenedor: ${st}]${NC}" + i=$((i+1)) + done + echo "" + echo -ne " ${O3}¿Qué servicio vas a construir? (1-${#options[@]} o nombre, Enter=cancelar):${NC} " + read -r choice + [[ -z "$choice" ]] && { warn "Cancelado"; return 0; } + + local svc="" + if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#options[@]} )); then + svc="${options[$((choice-1))]}" + else + for s in "${options[@]}"; do [[ "$s" == "$choice" ]] && svc="$s"; done + fi + [[ -z "$svc" ]] && { err "Opción inválida: ${choice}"; return 1; } + + local dir repo def_branch + dir="$(repo_dir "$svc")" + repo="$(repo_url "$svc")" + def_branch="$(repo_branch "$svc")" + + # 2. ¿Qué rama? + echo -ne " ${O3}Rama a construir [${def_branch}]:${NC} " + read -r branch + branch="${branch:-$def_branch}" + + # 3. Resumen + confirmación + local dockerfile pre_build="" + dockerfile="$(_image_dockerfile "$svc")" + case "$svc" in gateway|service-bus) pre_build="gradle bootJar → " ;; esac + echo "" + echo -e " ${O2}${BOLD}Plan de ejecución:${NC}" + echo -e " Servicio: ${WHITE}${svc}${NC}" + echo -e " Repo banco: ${WHITE}${repo}${NC}" + echo -e " Rama: ${WHITE}${branch}${NC}" + echo -e " Directorio: ${GRAY}${dir}${NC}" + echo -e " Dockerfile: ${GRAY}${dir}/${dockerfile:-Dockerfile}${NC}" + echo -e " Pasos: ${GRAY}git pull → ${pre_build}docker build → up -d → health${NC}" + echo "" + echo -ne " ${O3}¿Continuar? (s/N):${NC} " + read -r confirm + [[ ! "$confirm" =~ ^[sSyY] ]] && { warn "Cancelado"; return 0; } + + local start_time=$(date +%s) + + # PASO 1: Git — clonar o actualizar desde el GitLab del banco + step "1/4 GIT" "Descargando rama ${branch} desde el GitLab del banco" + if [[ ! -d "$dir/.git" ]]; then + info "Repo no existe localmente, clonando..." + mkdir -p "$(dirname "$dir")" + git clone -b "$branch" "$repo" "$dir" || { err "Clone falló: ${repo} (¿VPN al banco activa?)"; return 1; } + ok "Clonado en ${dir} (${branch})" + else + local remote + remote="$(_bank_remote "$dir")" + if [[ -z "$remote" ]]; then + warn "Ningún remote apunta a ${GITLAB_URL} — uso 'origin'" + remote="origin" + fi + cd "$dir" + info "Remote banco: ${remote} ($(git remote get-url "$remote" 2>/dev/null))" + git fetch "$remote" --prune || { err "Fetch falló (¿VPN al banco activa?)"; cd "$BDT_ROOT"; return 1; } + + if [[ -n "$(git status --porcelain 2>/dev/null | head -1)" ]]; then + warn "Hay cambios locales sin commitear en ${dir}" + echo -ne " ${O3}¿Guardar en stash y continuar? (s/N):${NC} " + read -r do_stash + if [[ "$do_stash" =~ ^[sSyY] ]]; then + git stash push -u -m "bdt-construir $(date '+%Y%m%d-%H%M%S')" && ok "Cambios guardados en stash" + else + err "Abortado para no pisar cambios locales" + cd "$BDT_ROOT"; return 1 + fi + fi + + git checkout "$branch" 2>/dev/null || git checkout -b "$branch" "${remote}/${branch}" 2>/dev/null || \ + { err "La rama '${branch}' no existe en ${remote}"; cd "$BDT_ROOT"; return 1; } + git pull "$remote" "$branch" && ok "Rama ${branch} @ $(git rev-parse --short HEAD)" || \ + { err "Pull falló"; cd "$BDT_ROOT"; return 1; } + cd "$BDT_ROOT" + fi + + # PASO 2: Compilación previa (solo Java: el Dockerfile copia build/libs/*.jar) + case "$svc" in + gateway|service-bus) + step "2/4 GRADLE" "Compilando ${svc} (bootJar, sin tests)" + cd "$dir" + if ./gradlew clean bootJar -x test > "${LOG_DIR}/${svc}-construir-gradle.log" 2>&1; then + ok "JAR generado: $(ls "${dir}"/build/libs/*.jar 2>/dev/null | head -1)" + else + err "Gradle falló (ver ${LOG_DIR}/${svc}-construir-gradle.log)" + tail -15 "${LOG_DIR}/${svc}-construir-gradle.log" | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + cd "$BDT_ROOT"; return 1 + fi + cd "$BDT_ROOT" + ;; + *) + step "2/4 BUILD" "Compilación dentro del Docker build (multi-stage)" + ok "Nada que compilar fuera del Docker" + ;; + esac + + # PASO 3: Imagen Docker + step "3/4 DOCKER" "Construyendo imagen bdt-${svc}:latest" + _docker_build_local "$svc" || return 1 + + # PASO 4: Recrear contenedor + health + step "4/4 UP" "Recreando contenedor ${svc}" + cd "$INFRA_DIR" + $COMPOSE_CMD --profile apps up -d --force-recreate "$svc" 2>&1 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done + cd "$BDT_ROOT" + + local url + url="$(svc_health_url "$svc")" + if [[ -n "$url" ]]; then + info "Esperando salud de ${svc} (hasta 120s)..." + local tries=0 code="000" + while (( tries < 24 )); do + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url" 2>/dev/null || echo "000") + [[ "$code" =~ ^(200|302|401)$ ]] && break + sleep 5; tries=$((tries+1)) + done + if [[ "$code" =~ ^(200|302|401)$ ]]; then + ok "${svc} arriba y saludable (HTTP ${code} en ${url})" + else + err "${svc} no responde tras $((tries*5))s (HTTP ${code}) — revisá: bdt logs ${svc}" + fi + fi + + local elapsed=$(( $(date +%s) - start_time )) + echo "" + ok "Asistente completado en ${elapsed}s — ${svc} @ ${branch}" +} + +#=============================================================================== +# AYUDA +#=============================================================================== + +cmd_help() { + banner + echo -e " ${O2}${BOLD}REPOSITORIOS${NC}" + echo -e " ${WHITE}clone${NC} [branch] Clonar todos los repos desde GitLab" + echo -e " ${WHITE}pull${NC} [branch] Actualizar todos los repos (git pull)" + echo "" + echo -e " ${O2}${BOLD}COMPILACIÓN${NC}" + echo -e " ${WHITE}install${NC} Instalar dependencias + compilar todo" + echo -e " ${WHITE}build${NC} [servicio|all] Construir imágenes Docker" + echo "" + echo -e " ${O2}${BOLD}SERVICIOS${NC}" + echo -e " ${WHITE}up${NC} [all|infra|apps|tools|nombre] Levantar servicios" + echo -e " ${WHITE}down${NC} [all|nombre] Detener servicios" + echo -e " ${WHITE}restart${NC} [all|nombre] Reiniciar servicios" + echo -e " ${WHITE}update${NC} [all|nombre] Pull + build + restart" + echo "" + echo -e " ${O2}${BOLD}MONITOREO${NC}" + echo -e " ${WHITE}status${NC} Estado de todos los servicios" + echo -e " ${WHITE}health${NC} Health check completo" + echo -e " ${WHITE}logs${NC} [N|-f] Ver logs (N=líneas, -f=seguir)" + echo -e " ${WHITE}logs all${NC} Logs combinados de todos" + echo "" + echo -e " ${O2}${BOLD}VERIFICACIÓN${NC}" + echo -e " ${WHITE}verify-db${NC} Verificar base de datos" + echo -e " ${WHITE}verify-api${NC} Verificar endpoints API" + echo "" + echo -e " ${O2}${BOLD}DEPLOYMENT${NC}" + echo -e " ${WHITE}construir${NC} Asistente interactivo: elegir servicio + rama → git banco → build → docker → up" + echo -e " ${WHITE}deploy${NC} Pipeline completo (pull→build→docker→up→health)" + echo -e " ${WHITE}env${NC} [ambiente] Cambiar ambiente (desarrollo|calidad|demo|produccion)" + echo "" + echo -e " ${O2}${BOLD}DOCKER REGISTRY (Gitea)${NC}" + echo -e " ${WHITE}image-login${NC} Docker login en ${GRAY}git.dcgeeks.net${NC}" + echo -e " ${WHITE}image-build${NC} [svc|all] Build local (tags: \$SHA + latest) — sin Gradle" + echo -e " ${WHITE}image-push${NC} [svc|all] Push a Gitea Registry" + echo -e " ${WHITE}image-pull${NC} [svc|all] [tag] Pull desde Gitea Registry" + echo -e " ${WHITE}image-deploy${NC} [svc|all] [tag] Pull + down + rm + up + health (server)" + echo -e " ${WHITE}image-build-amd64${NC} [svc|all] Build linux/amd64 + push al registry — sin Gradle" + echo -e " ${WHITE}server-deploy${NC} [svc|all] SCP + ejecutar deploy en NEWIB01-DEV via SSH" + echo "" + echo -e " ${O3}${BOLD}JAR pre-compilado (image-build / image-build-amd64):${NC}" + echo -e " Gradle NO corre. Copiá el JAR compilado ANTES de buildear:" + echo -e " ${GRAY}~/.bdt/jars/gateway/ ← gateway-*.jar${NC}" + echo -e " ${GRAY}~/.bdt/jars/service-bus/ ← service-bus.jar${NC}" + echo -e " ${GRAY}~/.bdt/jars/channels/ ← channels-*.jar${NC}" + echo -e " Docker build corre con --no-cache." + echo "" + echo -e " ${O2}${BOLD}UTILIDADES${NC}" + echo -e " ${WHITE}git-auth${NC} [user] [pass] Configurar credenciales Git (no pide clave en cada pull)" + echo -e " ${WHITE}setup${NC} Instalar 'bdt' como comando global + alias + autocompletado" + echo -e " ${WHITE}menu${NC} Menu interactivo (panel de control)" + echo -e " ${WHITE}creds${NC} Mostrar credenciales" + echo -e " ${WHITE}clean${NC} Limpiar Docker (contenedores, imágenes)" + echo -e " ${WHITE}help${NC} Mostrar esta ayuda" + echo "" + echo -e " ${O3}${BOLD}EJEMPLOS:${NC}" + echo -e " ${GRAY}./bdt.sh deploy${NC} Deploy completo" + echo -e " ${GRAY}./bdt.sh up infra && ./bdt.sh up apps${NC} Levantar por partes" + echo -e " ${GRAY}./bdt.sh logs gateway -f${NC} Seguir logs del gateway" + echo -e " ${GRAY}./bdt.sh update gateway${NC} Actualizar solo gateway" + echo -e " ${GRAY}./bdt.sh clone calidad${NC} Clonar rama calidad" + echo "" +} + +#=============================================================================== +# MAIN +#=============================================================================== + +cmd="${1:-menu}" +shift 2>/dev/null || true + +case "$cmd" in + clone) cmd_clone "$@" ;; + pull) cmd_pull "$@" ;; + install) cmd_install "$@" ;; + build) cmd_build "$@" ;; + up|start) cmd_up "$@" ;; + down|stop) cmd_down "$@" ;; + restart) cmd_restart "$@" ;; + status|st) cmd_status "$@" ;; + logs|log) cmd_logs "$@" ;; + health|hc) cmd_health "$@" ;; + verify-db) cmd_verify_db "$@" ;; + verify-api) cmd_verify_api "$@" ;; + env) cmd_env "$@" ;; + update) cmd_update "$@" ;; + deploy) cmd_deploy "$@" ;; + construir|wizard|asistente) cmd_construir "$@" ;; + creds) cmd_creds "$@" ;; + clean) cmd_clean "$@" ;; + git-auth) cmd_git_auth "$@" ;; + setup) cmd_setup "$@" ;; + menu) cmd_menu "$@" ;; + image-login) cmd_image_login "$@" ;; + image-build) cmd_image_build "$@" ;; + image-push) cmd_image_push "$@" ;; + image-pull) cmd_image_pull "$@" ;; + image-deploy) cmd_image_deploy "$@" ;; + image-build-amd64) cmd_image_build_amd64 "$@" ;; + server-deploy) cmd_server_deploy "$@" ;; + help|-h|--help) cmd_help "$@" ;; + *) + err "Comando desconocido: ${cmd}" + echo "" + echo " Usa: ${GRAY}./bdt.sh help${NC}" + exit 1 + ;; +esac