#!/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.3.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 nocache="${2:-}" 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") if [[ "$nocache" == "nocache" || "$nocache" == "--no-cache" ]]; then args=(--no-cache "${args[@]}") info "Construyendo bdt-${svc}:latest ${YELLOW}(sin caché)${NC}..." else info "Construyendo bdt-${svc}:latest..." fi [[ -n "$dockerfile" ]] && args+=(-f "$dockerfile_path") args+=("$dir") 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="all" nocache="" a # Soporta: bdt build [servicio|all] [--no-cache] for a in "$@"; do case "$a" in --no-cache|nocache) nocache="nocache" ;; *) svc="$a" ;; esac done step "BUILD" "Construyendo imágenes Docker${nocache:+ (sin caché)}" if [[ "$svc" == "all" ]]; then for s in $APP_SERVICES; do _docker_build_local "$s" "$nocache" || true done else _docker_build_local "$svc" "$nocache" fi } #=============================================================================== # COMANDO: rebuild - Interactivo: elegir docker → build --no-cache → up # La idea del shell: seleccionar CUÁL docker reconstruir SIN CACHÉ y levantarlo. #=============================================================================== cmd_rebuild() { banner step "REBUILD" "Reconstruir un servicio SIN CACHÉ y levantarlo (--force-recreate)" echo "" # 1. Elegir servicio (lista numerada con estado del contenedor) 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}[contenedor: ${st}]${NC}" i=$((i+1)) done echo -e " ${WHITE}a${NC}) TODOS los servicios de aplicación" echo "" echo -ne " ${O3}¿Qué docker reconstruir sin caché? (1-${#options[@]}, 'a'=todos, nombre, Enter=cancelar):${NC} " read -r choice [[ -z "$choice" ]] && { warn "Cancelado"; return 0; } local targets=() if [[ "$choice" == "a" || "$choice" == "all" ]]; then targets=($APP_SERVICES) elif [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#options[@]} )); then targets=("${options[$((choice-1))]}") else for s in "${options[@]}"; do [[ "$s" == "$choice" ]] && targets=("$s"); done fi [[ ${#targets[@]} -eq 0 ]] && { err "Opción inválida: ${choice}"; return 1; } # 2. Servicios Java: el Dockerfile copia build/libs/*.jar — ofrecer recompilar el JAR local recompile="" for s in "${targets[@]}"; do case "$s" in gateway|service-bus) echo -ne " ${O3}¿Recompilar el JAR (gradle bootJar) antes del build? (s/N):${NC} " read -r recompile break ;; esac done # 3. Por cada target: (gradle) → docker build --no-cache → up --force-recreate → health for s in "${targets[@]}"; do echo "" step "REBUILD ${s}" "$(svc_desc "$s")" if [[ "$recompile" =~ ^[sSyY] ]]; then case "$s" in gateway|service-bus) local jdir; jdir="$(repo_dir "$s")" info "Compilando ${s} (bootJar, sin tests)..." ( cd "$jdir" && ./gradlew clean bootJar -x test > "${LOG_DIR}/${s}-rebuild-gradle.log" 2>&1 ) \ && ok "JAR: $(ls "${jdir}"/build/libs/*.jar 2>/dev/null | head -1)" \ || { err "Gradle falló (ver ${LOG_DIR}/${s}-rebuild-gradle.log)"; continue; } ;; esac fi _docker_build_local "$s" nocache || { err "Build de ${s} falló — sigo con el resto"; continue; } info "Recreando contenedor ${s}..." ( cd "$INFRA_DIR" && $COMPOSE_CMD --profile apps --profile tools up -d --force-recreate "$s" 2>&1 \ | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done ) local url; url="$(svc_health_url "$s")" if [[ -n "$url" ]]; then info "Esperando salud de ${s} (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 [[ "$code" =~ ^(200|302|401)$ ]] && ok "${s} saludable (HTTP ${code})" || warn "${s}: sin health OK (HTTP ${code}) — revisá logs" else ok "${s} recreado" fi done cd "$BDT_ROOT" 2>/dev/null || true } #=============================================================================== # COMANDO: jar-deploy - Subir el JAR local a un server destino y reconstruir allá # Para servicios Java (gateway, service-bus): compila/usa el JAR local, lo sube # por SCP a build/libs del server y corre 'bdt build + up' remoto (vía SSH+sudo). # Ideal para NEWIB01-DEV (.133), que NO alcanza el registry de Gitea. # Config en ~/.bdt/config: DEPLOY_SERVER, DEPLOY_USER, BDT_REMOTE_ROOT (def /opt/bdt-platform). #=============================================================================== cmd_jar_deploy() { banner local svc="" nocache="" do_build="" a for a in "$@"; do case "$a" in --no-cache|nocache) nocache="--no-cache" ;; --build) do_build="1" ;; *) svc="$a" ;; esac done case "$svc" in gateway|service-bus) ;; *) err "jar-deploy es solo para servicios Java: gateway | service-bus" echo -e " Uso: ${GRAY}bdt jar-deploy [--build] [--no-cache]${NC}" return 1 ;; esac local server="${DEPLOY_SERVER:-}" user="${DEPLOY_USER:-}" local rroot="${BDT_REMOTE_ROOT:-/opt/bdt-platform}" if [[ -z "$server" || -z "$user" ]]; then err "Falta el server destino. Agregá en ${GRAY}~/.bdt/config${NC}:" echo -e " ${GRAY}DEPLOY_SERVER=10.112.2.133${NC}" echo -e " ${GRAY}DEPLOY_USER=luis.perezsm${NC}" echo -e " ${GRAY}BDT_REMOTE_ROOT=/opt/bdt-platform${NC} (opcional)" return 1 fi local dir; dir="$(repo_dir "$svc")" [[ -z "$dir" ]] && { err "Servicio desconocido: ${svc}"; return 1; } # 1. JAR local (compilar si se pide --build o si no existe) step "1/4 JAR" "Resolviendo JAR local de ${svc}" local jar jar="$(ls "${dir}"/build/libs/*.jar 2>/dev/null | grep -v -- '-plain.jar' | head -1)" if [[ -n "$do_build" || -z "$jar" ]]; then info "Compilando ${svc} (gradle bootJar, sin tests)..." ( cd "$dir" && ./gradlew clean bootJar -x test > "${LOG_DIR}/${svc}-jardeploy-gradle.log" 2>&1 ) \ || { err "Gradle falló (ver ${LOG_DIR}/${svc}-jardeploy-gradle.log)"; return 1; } jar="$(ls "${dir}"/build/libs/*.jar 2>/dev/null | grep -v -- '-plain.jar' | head -1)" fi [[ -z "$jar" ]] && { err "No hay JAR en ${dir}/build/libs (corré con --build)"; return 1; } local jar_base; jar_base="$(basename "$jar")" ok "JAR: ${jar} ($(du -h "$jar" | cut -f1))" # 2. SCP al server local remote_libs="${rroot}/backend/${svc}/build/libs" step "2/4 SCP" "Subiendo ${jar_base} → ${user}@${server}:${remote_libs}" scp "$jar" "${user}@${server}:/tmp/${svc}-deploy.jar" || { err "SCP falló (¿VPN/SSH al server activa?)"; return 1; } ok "JAR subido a /tmp" # 3. Colocar el JAR (solo uno) en build/libs del server step "3/4 PLACE" "Colocando JAR en ${remote_libs} (limpia .jar previos)" ssh "${user}@${server}" "sudo mkdir -p '${remote_libs}' && sudo rm -f '${remote_libs}'/*.jar && sudo cp /tmp/${svc}-deploy.jar '${remote_libs}/${jar_base}' && rm -f /tmp/${svc}-deploy.jar && echo OK" \ || { err "No se pudo colocar el JAR en el server"; return 1; } ok "JAR colocado: ${remote_libs}/${jar_base}" # 4. Build + up remoto (usa el bdt.sh del server) step "4/4 REMOTE" "docker build${nocache:+ (sin caché)} + up de ${svc} en ${server}" ssh "${user}@${server}" "cd '${rroot}' && sudo ./bdt.sh build ${svc} ${nocache} && sudo ./bdt.sh up ${svc}" 2>&1 \ | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done echo "" ok "Deploy de ${svc} enviado a ${server}. Verificá salud con: ${GRAY}bdt logs ${svc} 100${NC} (o el health del server)." } #=============================================================================== # 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 rebuild jar-deploy up down restart status logs health verify-db verify-api deploy construir publish 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 ;; n) cmd_rebuild ;; p) echo -ne " Servicio Java a subir (gateway/service-bus): " read -r jd_svc echo -ne " ¿Build sin caché? (s/N): " read -r jd_nc if [[ -n "$jd_svc" ]]; then if [[ "$jd_nc" =~ ^[sSyY] ]]; then cmd_jar_deploy "$jd_svc" --build --no-cache; else cmd_jar_deploy "$jd_svc" --build; fi fi ;; 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 -e " ${O1}${BOLD}n) REBUILD SIN CACHÉ${NC} ${GRAY}— elegir docker → build --no-cache → up --force-recreate${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 -e " ${WHITE}p${NC}) Subir JAR al server (jar-deploy: scp jar → build/libs → build+up remoto)" 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}" } #=============================================================================== # COMANDO: publish - Sincronizar bdt.sh al repo público bdt-cli (Gitea) #=============================================================================== cmd_publish() { banner step "PUBLISH" "Sincronizando bdt.sh → repo público bdt-cli" # Ubicación del clon de bdt-cli: por defecto, al lado de la raíz del proyecto. # Override en ~/.bdt/config: BDT_CLI_DIR=/ruta/al/clon local cli_dir="${BDT_CLI_DIR:-$(dirname "$BDT_ROOT")/bdt-cli}" if [[ ! -d "${cli_dir}/.git" ]]; then err "No existe el clon de bdt-cli en ${cli_dir}" echo -e " Clonalo: ${GRAY}git clone https://git.dcgeeks.net/BDT/bdt-cli.git ${cli_dir}${NC}" echo -e " O definí ${GRAY}BDT_CLI_DIR=/ruta${NC} en ~/.bdt/config" return 1 fi cp "${BDT_ROOT}/bdt.sh" "${cli_dir}/bdt.sh" chmod +x "${cli_dir}/bdt.sh" cd "$cli_dir" if git diff --quiet -- bdt.sh 2>/dev/null && [[ -z "$(git status --porcelain bdt.sh)" ]]; then ok "Sin cambios: bdt-cli ya está al día (v${SCRIPT_VERSION})" cd "$BDT_ROOT" return 0 fi local src_sha src_sha="$(git -C "$BDT_ROOT" rev-parse --short HEAD 2>/dev/null || echo local)" git add bdt.sh git commit -q -m "chore: sync bdt.sh v${SCRIPT_VERSION} (monorepo @ ${src_sha})" && \ ok "Commit creado: v${SCRIPT_VERSION} @ ${src_sha}" || { err "Commit falló"; cd "$BDT_ROOT"; return 1; } info "Pusheando a Gitea..." if git push origin main 2>&1 | tail -2 | while read -r l; do echo -e " ${GRAY}${l}${NC}"; done; then ok "Publicado: https://git.dcgeeks.net/BDT/bdt-cli" else err "Push falló — corré 'git push origin main' en ${cli_dir}" cd "$BDT_ROOT"; return 1 fi cd "$BDT_ROOT" } #=============================================================================== # 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] [--no-cache] Construir imágenes Docker (opcional sin caché)" echo -e " ${WHITE}rebuild${NC} Interactivo: elegir docker → build --no-cache → up --force-recreate" 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 (vía registry)" echo -e " ${WHITE}jar-deploy${NC} [--build] [--no-cache]" echo -e " Sube TU jar local al server (DEPLOY_SERVER) → build/libs → build+up remoto." echo -e " Útil para .133 (no usa el registry de Gitea). Requiere ~/.bdt/config + sudo remoto." 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}publish${NC} Sincronizar bdt.sh al repo público bdt-cli (Gitea)" 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 "$@" ;; rebuild|rebuild-nc) cmd_rebuild "$@" ;; jar-deploy|jardeploy) cmd_jar_deploy "$@" ;; 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 "$@" ;; publish) cmd_publish "$@" ;; 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