#!/bin/bash # ============================================ # ZoeFlexSolutions - Instalador v2.7 (Swarm) # https://setup.zoeflexsolutions.com # ============================================ # Detecta TUDO automaticamente do servidor # Deploy via Docker Swarm (stack deploy) # Auto-corrige caracteres Windows # ============================================ # Auto-correcao: Se executado via curl, baixa e re-executa limpo if [ "$ZOEFLEX_CLEAN" != "1" ]; then SCRIPT_URL="https://setup.zoeflexsolutions.com" TEMP_SCRIPT="/tmp/zoeflex_setup_$$.sh" curl -sSL "$SCRIPT_URL" -o "$TEMP_SCRIPT" 2>/dev/null || wget -qO "$TEMP_SCRIPT" "$SCRIPT_URL" 2>/dev/null sed -i 's/\r$//' "$TEMP_SCRIPT" 2>/dev/null || true chmod +x "$TEMP_SCRIPT" ZOEFLEX_CLEAN=1 exec bash "$TEMP_SCRIPT" "$@" fi set -e # Cores RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' # Funcoes de log log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warning() { echo -e "${YELLOW}[!]${NC} $1"; } log_error() { echo -e "${RED}[X]${NC} $1"; } log_found() { echo -e "${GREEN}[ENCONTRADO]${NC} $1"; } # Banner clear echo "" echo -e "${CYAN}============================================${NC}" echo -e "${CYAN} ZOEFLEX SOLUTIONS - Instalador v2.7 ${NC}" echo -e "${CYAN}============================================${NC}" echo -e "${YELLOW} Docker Swarm - Stack Deploy ${NC}" echo -e "${CYAN}============================================${NC}" echo "" # URL base dos arquivos BASE_URL="https://setup.zoeflexsolutions.com" # Variaveis globais para configuracoes detectadas DETECTED_PG_HOST="" DETECTED_PG_PORT="5432" DETECTED_PG_USER="postgres" DETECTED_PG_PASS="" DETECTED_PG_TYPE="postgresql" DETECTED_EVO_URL="" DETECTED_EVO_KEY="" DETECTED_EVO_INSTANCE="" DETECTED_NETWORK="" DETECTED_DOMAIN_BASE="" # Verificar root if [ "$EUID" -ne 0 ]; then log_error "Execute como root: sudo bash <(curl -sSL setup.zoeflexsolutions.com)" exit 1 fi # ============================================ # FUNCOES DE DETECCAO SUPER INTELIGENTE # ============================================ extract_value() { local file="$1" local pattern="$2" local value="" if [ -f "$file" ]; then value=$(grep -iE "^\s*${pattern}\s*[=:]" "$file" 2>/dev/null | grep -v "^#" | head -1 | sed 's/.*[=:]\s*//' | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d '\r') fi echo "$value" } auto_detect_postgres() { log_info "Detectando PostgreSQL automaticamente..." echo "" local yaml_files=$(find /root -maxdepth 1 -name "*.yaml" -o -name "*.yml" 2>/dev/null) local env_files=$(find /root -maxdepth 1 -name ".env*" 2>/dev/null) local opt_files=$(find /opt -maxdepth 2 -name "*.yaml" -o -name "*.yml" -o -name ".env*" 2>/dev/null) for file in $yaml_files $env_files $opt_files /root/pgvector.yaml /root/postgres.yaml; do if [ -f "$file" ]; then local pass=$(extract_value "$file" "POSTGRES_PASSWORD") [ -z "$pass" ] && pass=$(extract_value "$file" "PG_PASSWORD") [ -z "$pass" ] && pass=$(extract_value "$file" "DB_PASSWORD") if [ -n "$pass" ] && [ ${#pass} -gt 5 ]; then DETECTED_PG_PASS="$pass" log_found "Senha PostgreSQL em: $file" fi local user=$(extract_value "$file" "POSTGRES_USER") [ -z "$user" ] && user=$(extract_value "$file" "PG_USER") [ -n "$user" ] && DETECTED_PG_USER="$user" local host=$(extract_value "$file" "POSTGRES_HOST") [ -z "$host" ] && host=$(extract_value "$file" "PG_HOST") [ -z "$host" ] && host=$(extract_value "$file" "DB_HOST") [ -n "$host" ] && DETECTED_PG_HOST="$host" fi done local pg_containers=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "postgres|pgvector|pg_" | head -5) if [ -n "$pg_containers" ]; then echo -e "${CYAN}Containers PostgreSQL encontrados:${NC}" local count=1 echo "$pg_containers" | while read container; do local ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container" 2>/dev/null | head -1) echo " $count) $container ${ip:+(IP: $ip)}" count=$((count + 1)) done echo "" [ -z "$DETECTED_PG_HOST" ] && DETECTED_PG_HOST=$(echo "$pg_containers" | head -1) local first_container=$(echo "$pg_containers" | head -1) if [ -z "$DETECTED_PG_PASS" ]; then local env_pass=$(docker exec "$first_container" printenv POSTGRES_PASSWORD 2>/dev/null || true) if [ -n "$env_pass" ]; then DETECTED_PG_PASS="$env_pass" log_found "Senha obtida do container: $first_container" fi fi if [ -z "$DETECTED_PG_USER" ] || [ "$DETECTED_PG_USER" = "postgres" ]; then local env_user=$(docker exec "$first_container" printenv POSTGRES_USER 2>/dev/null || true) [ -n "$env_user" ] && DETECTED_PG_USER="$env_user" fi fi local mysql_containers=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "mysql|mariadb" | head -1) if [ -n "$mysql_containers" ] && [ -z "$pg_containers" ]; then DETECTED_PG_TYPE="mysql" DETECTED_PG_HOST="$mysql_containers" DETECTED_PG_PORT="3306" log_warning "MySQL detectado ao inves de PostgreSQL" local mysql_pass=$(docker exec "$mysql_containers" printenv MYSQL_ROOT_PASSWORD 2>/dev/null || true) if [ -n "$mysql_pass" ]; then DETECTED_PG_PASS="$mysql_pass" DETECTED_PG_USER="root" fi fi log_success "PostgreSQL: $DETECTED_PG_USER@$DETECTED_PG_HOST:$DETECTED_PG_PORT" } auto_detect_evolution() { log_info "Detectando Evolution API automaticamente..." echo "" local yaml_files=$(find /root -maxdepth 1 -name "*.yaml" -o -name "*.yml" 2>/dev/null) for file in $yaml_files /root/evolution.yaml /root/evolution.yml; do if [ -f "$file" ]; then if grep -qi "evolution" "$file" 2>/dev/null; then log_found "Arquivo Evolution: $file" local key=$(extract_value "$file" "AUTHENTICATION_API_KEY") [ -z "$key" ] && key=$(grep -iE "AUTHENTICATION_API_KEY|apikey" "$file" 2>/dev/null | grep -v "^#" | head -1 | grep -oE "[a-f0-9]{32}" || true) if [ -n "$key" ] && [ ${#key} -gt 10 ]; then DETECTED_EVO_KEY="$key" log_found "API Key: ${key:0:10}..." fi local domain=$(grep -oP "Host\(\`\K[^\`]+" "$file" 2>/dev/null | head -1 || true) if [ -n "$domain" ]; then DETECTED_EVO_URL="https://$domain" log_found "URL: $DETECTED_EVO_URL" fi fi fi done local evo_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "evolution.*api|evolution_evolution" | grep -v redis | head -1 || true) if [ -n "$evo_container" ]; then log_found "Container Evolution: $evo_container" local evo_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$evo_container" 2>/dev/null | head -1) if [ -z "$DETECTED_EVO_URL" ]; then [ -n "$evo_ip" ] && DETECTED_EVO_URL="http://${evo_ip}:8080" || DETECTED_EVO_URL="http://${evo_container}:8080" fi if [ -z "$DETECTED_EVO_KEY" ]; then local env_key=$(docker exec "$evo_container" printenv AUTHENTICATION_API_KEY 2>/dev/null || true) if [ -n "$env_key" ]; then DETECTED_EVO_KEY="$env_key" log_found "API Key obtida do container" fi fi if [ -n "$DETECTED_EVO_KEY" ] && [ -n "$DETECTED_EVO_URL" ]; then log_info "Buscando instancias Evolution..." local instances=$(curl -s --max-time 5 -X GET "${DETECTED_EVO_URL}/instance/fetchInstances" \ -H "apikey: ${DETECTED_EVO_KEY}" 2>/dev/null | grep -oP '"instanceName"\s*:\s*"\K[^"]+' || true) if [ -n "$instances" ]; then echo -e "${CYAN}Instancias encontradas:${NC}" echo "$instances" | while read inst; do echo " - $inst"; done DETECTED_EVO_INSTANCE=$(echo "$instances" | head -1) log_found "Instancia: $DETECTED_EVO_INSTANCE" fi fi fi if [ -z "$DETECTED_EVO_URL" ] || [[ "$DETECTED_EVO_URL" == *"localhost"* ]] || [[ "$DETECTED_EVO_URL" == *"127.0.0.1"* ]]; then local evo_domain=$(docker inspect $(docker ps -q) 2>/dev/null | grep -oP 'Host\(`\K[^`]+' | grep -i "evo" | head -1 || true) [ -n "$evo_domain" ] && DETECTED_EVO_URL="https://$evo_domain" && log_found "URL Evolution via Traefik: $DETECTED_EVO_URL" fi } auto_detect_network() { log_info "Detectando rede Docker do Traefik..." # Método 1: Verificar configuração do Traefik (mais confiável) local traefik_service=$(docker service ls --format '{{.Name}}' 2>/dev/null | grep -i traefik | head -1 || true) if [ -n "$traefik_service" ]; then log_found "Serviço Traefik: $traefik_service" # Pegar a rede configurada no provider.docker.network local configured_network=$(docker service inspect "$traefik_service" --format '{{range .Spec.TaskTemplate.ContainerSpec.Args}}{{println .}}{{end}}' 2>/dev/null | grep -oP 'providers\.docker\.network=\K[^\s]+' || true) if [ -n "$configured_network" ]; then DETECTED_NETWORK="$configured_network" log_found "Rede configurada no Traefik: $DETECTED_NETWORK" return fi fi # Método 2: Verificar qual rede o container Traefik está usando local traefik_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i traefik | head -1 || true) if [ -n "$traefik_container" ]; then log_found "Container Traefik: $traefik_container" # Pegar todas as redes do Traefik (exceto ingress) local traefik_networks=$(docker inspect "$traefik_container" 2>/dev/null | grep -oP '"Name":\s*"\K[^"]+' | grep -v "^$traefik_container$" | grep -v "ingress" || true) # Priorizar redes com nomes específicos for net in $traefik_networks; do if echo "$net" | grep -qiE "sandbox|traefik|proxy|web|public"; then DETECTED_NETWORK="$net" local net_driver=$(docker network inspect "$net" --format '{{.Driver}}' 2>/dev/null || true) local net_scope=$(docker network inspect "$net" --format '{{.Scope}}' 2>/dev/null || true) log_found "Rede do Traefik: $DETECTED_NETWORK (driver: $net_driver, scope: $net_scope)" return fi done # Se não encontrou rede específica, usar a primeira rede disponível if [ -n "$traefik_networks" ]; then DETECTED_NETWORK=$(echo "$traefik_networks" | head -1) log_found "Rede do Traefik: $DETECTED_NETWORK" return fi fi # Método 3: Procurar por redes overlay com nomes conhecidos local networks=$(docker network ls --format '{{.Name}}' 2>/dev/null | grep -iE "sandbox|traefik|proxy" || true) if [ -n "$networks" ]; then DETECTED_NETWORK=$(echo "$networks" | head -1) log_found "Rede encontrada: $DETECTED_NETWORK" return fi # Se não encontrou nada, usar padrão DETECTED_NETWORK="traefik_default" log_warning "Nenhuma rede do Traefik encontrada. Usando padrão: $DETECTED_NETWORK" } ensure_network_exists() { local network_name="$1" # Verificar se a rede existe if docker network ls --format '{{.Name}}' | grep -q "^${network_name}$"; then log_success "Rede $network_name ja existe" return 0 fi # Se não existe, tentar criar como overlay attachable log_warning "Rede $network_name nao existe. Tentando criar..." if docker network create --driver overlay --attachable "$network_name" 2>/dev/null; then log_success "Rede $network_name criada com sucesso" return 0 else log_error "Erro ao criar rede $network_name" log_warning "A rede sera criada automaticamente pelo Docker Swarm" return 0 fi } auto_detect_domain() { log_info "Detectando dominios..." local domains=$(docker inspect $(docker ps -q) 2>/dev/null | grep -oP 'Host\(`\K[^`]+' | sort -u || true) if [ -n "$domains" ]; then local first_domain=$(echo "$domains" | head -1) DETECTED_DOMAIN_BASE=$(echo "$first_domain" | sed 's/^[^.]*\.//') log_found "Dominio base: $DETECTED_DOMAIN_BASE" echo "" echo -e "${CYAN}Dominios encontrados no servidor:${NC}" echo "$domains" | while read d; do echo " - $d"; done fi } check_infrastructure() { echo "" log_info "Verificando infraestrutura..." echo "" local all_ok=true if command -v docker &> /dev/null; then log_success "Docker instalado" else log_error "Docker NAO encontrado" all_ok=false fi # Verificar Swarm if docker info 2>/dev/null | grep -q "Swarm: active"; then log_success "Docker Swarm ativo" else log_warning "Docker Swarm nao esta ativo" log_info "Ativando Swarm..." docker swarm init 2>/dev/null || docker swarm init --advertise-addr $(hostname -I | awk '{print $1}') 2>/dev/null || true fi if docker ps --format '{{.Names}}' | grep -qi "traefik"; then log_success "Traefik rodando" else log_warning "Traefik nao encontrado (SSL pode nao funcionar)" fi if docker ps --format '{{.Names}}' | grep -qiE "(postgres|pgvector)"; then log_success "PostgreSQL encontrado" else log_warning "PostgreSQL nao encontrado" fi if docker ps --format '{{.Names}}' | grep -qi "portainer"; then log_success "Portainer rodando" else log_warning "Portainer nao encontrado" fi if [ "$all_ok" = false ]; then echo "" log_error "Infraestrutura basica incompleta!" echo -e "${YELLOW}Execute primeiro: bash <(curl -sSL setup.oriondesign.art.br)${NC}" echo "" read -p "Continuar mesmo assim? (s/N): " cont [ "$cont" != "s" ] && [ "$cont" != "S" ] && exit 1 fi } # ============================================ # MENU PRINCIPAL # ============================================ show_menu() { echo "" echo -e "${YELLOW}============================================${NC}" echo -e "${YELLOW} O QUE DESEJA INSTALAR? ${NC}" echo -e "${YELLOW}============================================${NC}" echo "" echo -e " ${CYAN}1)${NC} Inbox ZoeSolutions - WhatsApp Manager" echo -e " ${CYAN}2)${NC} [Em breve] CRM ZoeSolutions" echo -e " ${CYAN}3)${NC} [Em breve] Automacao ZoeSolutions" echo "" echo -e " ${CYAN}0)${NC} Sair" echo "" read -p "Escolha uma opcao: " OPTION case $OPTION in 1) install_inbox ;; 2) echo -e "${YELLOW}Em desenvolvimento...${NC}"; show_menu ;; 3) echo -e "${YELLOW}Em desenvolvimento...${NC}"; show_menu ;; 0) echo "Ate logo!"; exit 0 ;; *) echo -e "${RED}Opcao invalida${NC}"; show_menu ;; esac } # ============================================ # INSTALACAO DO INBOX (SWARM) # ============================================ install_inbox() { echo "" echo -e "${MAGENTA}============================================${NC}" echo -e "${MAGENTA} INBOX ZOESOLUTIONS - WhatsApp Manager ${NC}" echo -e "${MAGENTA}============================================${NC}" echo "" echo -e "${CYAN}Coletando informacoes automaticamente...${NC}" echo "" auto_detect_postgres echo "" auto_detect_evolution echo "" auto_detect_network auto_detect_domain echo "" echo -e "${GREEN}============================================${NC}" echo -e "${GREEN} CONFIGURACOES DETECTADAS ${NC}" echo -e "${GREEN}============================================${NC}" echo "" echo -e "${CYAN}Banco de Dados (${DETECTED_PG_TYPE}):${NC}" echo " Host: ${DETECTED_PG_HOST:-NAO DETECTADO}" echo " Porta: ${DETECTED_PG_PORT}" echo " Usuario: ${DETECTED_PG_USER}" echo " Senha: ${DETECTED_PG_PASS:+[DETECTADA]}" [ -z "$DETECTED_PG_PASS" ] && echo " Senha: NAO DETECTADA" echo "" echo -e "${CYAN}Evolution API:${NC}" echo " URL: ${DETECTED_EVO_URL:-NAO DETECTADO}" echo " API Key: ${DETECTED_EVO_KEY:+[DETECTADA]}" [ -z "$DETECTED_EVO_KEY" ] && echo " API Key: NAO DETECTADA" echo " Instancia: ${DETECTED_EVO_INSTANCE:-NAO DETECTADO}" echo "" echo -e "${CYAN}Docker:${NC}" echo " Rede: ${DETECTED_NETWORK}" echo " Dominio base: ${DETECTED_DOMAIN_BASE:-NAO DETECTADO}" echo "" local all_detected=true [ -z "$DETECTED_PG_HOST" ] && all_detected=false [ -z "$DETECTED_PG_PASS" ] && all_detected=false [ -z "$DETECTED_EVO_URL" ] && all_detected=false [ -z "$DETECTED_EVO_KEY" ] && all_detected=false if [ "$all_detected" = true ]; then echo -e "${GREEN}Todas as configuracoes foram detectadas automaticamente!${NC}" else echo -e "${YELLOW}Algumas configuracoes precisam ser informadas manualmente.${NC}" fi echo "" echo -e "${YELLOW}============================================${NC}" echo -e "${YELLOW} CONFIGURACAO DA INSTALACAO ${NC}" echo -e "${YELLOW}============================================${NC}" echo "" read -p "Nome da empresa (sem espacos, ex: minhaempresa): " COMPANY_NAME if [ -z "$COMPANY_NAME" ]; then log_error "Nome da empresa e obrigatorio" exit 1 fi COMPANY_SLUG=$(echo "$COMPANY_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g') read -p "Nome de exibicao [Inbox $COMPANY_NAME]: " APP_TITLE APP_TITLE="${APP_TITLE:-Inbox $COMPANY_NAME}" local suggested_domain="" [ -n "$DETECTED_DOMAIN_BASE" ] && suggested_domain="inbox-${COMPANY_SLUG}.${DETECTED_DOMAIN_BASE}" echo "" echo -e "${CYAN}Exemplos de dominio (SEM https://):${NC}" echo " - inbox.minhaempresa.com.br" echo " - chat.empresa.com.br" [ -n "$suggested_domain" ] && echo -e " - ${GREEN}$suggested_domain (sugerido)${NC}" echo "" read -p "Dominio para esta instalacao${suggested_domain:+ [$suggested_domain]}: " DOMAIN DOMAIN="${DOMAIN:-$suggested_domain}" # Remover https:// ou http:// se o usuario digitou DOMAIN=$(echo "$DOMAIN" | sed 's|^https://||' | sed 's|^http://||') if [ -z "$DOMAIN" ]; then log_error "Dominio e obrigatorio" exit 1 fi echo "" echo -e "${CYAN}Configuracao do Banco de Dados:${NC}" if [ -n "$DETECTED_PG_HOST" ] && [ -n "$DETECTED_PG_PASS" ]; then echo -e "${GREEN}Configuracao detectada automaticamente!${NC}" echo " Host: $DETECTED_PG_HOST" echo " Usuario: $DETECTED_PG_USER" echo " Senha: ********" read -p "Usar configuracao detectada? (S/n): " use_detected_pg if [ "$use_detected_pg" = "n" ] || [ "$use_detected_pg" = "N" ]; then read -p "Host: " DB_HOST read -p "Porta [5432]: " DB_PORT read -p "Usuario [postgres]: " DB_USER read -sp "Senha: " DB_PASS echo "" DB_PORT="${DB_PORT:-5432}" DB_USER="${DB_USER:-postgres}" else DB_HOST="$DETECTED_PG_HOST" DB_PORT="$DETECTED_PG_PORT" DB_USER="$DETECTED_PG_USER" DB_PASS="$DETECTED_PG_PASS" fi else read -p "Host [${DETECTED_PG_HOST:-localhost}]: " DB_HOST DB_HOST="${DB_HOST:-${DETECTED_PG_HOST:-localhost}}" read -p "Porta [${DETECTED_PG_PORT:-5432}]: " DB_PORT DB_PORT="${DB_PORT:-${DETECTED_PG_PORT:-5432}}" read -p "Usuario [${DETECTED_PG_USER:-postgres}]: " DB_USER DB_USER="${DB_USER:-${DETECTED_PG_USER:-postgres}}" read -sp "Senha: " DB_PASS echo "" fi echo "" echo -e "${CYAN}Configuracao da Evolution API:${NC}" if [ -n "$DETECTED_EVO_URL" ] && [ -n "$DETECTED_EVO_KEY" ]; then echo -e "${GREEN}Configuracao detectada automaticamente!${NC}" echo " URL: $DETECTED_EVO_URL" echo " API Key: ${DETECTED_EVO_KEY:0:10}..." read -p "Usar configuracao detectada? (S/n): " use_detected_evo if [ "$use_detected_evo" = "n" ] || [ "$use_detected_evo" = "N" ]; then read -p "URL Evolution API: " EVO_URL read -p "API Key: " EVO_KEY read -p "Nome da instancia: " EVO_INSTANCE else EVO_URL="$DETECTED_EVO_URL" EVO_KEY="$DETECTED_EVO_KEY" EVO_INSTANCE="${DETECTED_EVO_INSTANCE}" [ -z "$EVO_INSTANCE" ] && read -p "Nome da instancia Evolution: " EVO_INSTANCE fi else read -p "URL Evolution API [${DETECTED_EVO_URL:-https://evo.seudominio.com}]: " EVO_URL EVO_URL="${EVO_URL:-$DETECTED_EVO_URL}" read -p "API Key Evolution: " EVO_KEY EVO_KEY="${EVO_KEY:-$DETECTED_EVO_KEY}" read -p "Nome da instancia [${DETECTED_EVO_INSTANCE}]: " EVO_INSTANCE EVO_INSTANCE="${EVO_INSTANCE:-$DETECTED_EVO_INSTANCE}" fi # Garantir que URL tem https:// [[ "$EVO_URL" != http* ]] && EVO_URL="https://$EVO_URL" NETWORK_NAME="${DETECTED_NETWORK:-traefik_public}" # Porta automatica LAST_PORT=$(docker ps --format '{{.Ports}}' 2>/dev/null | grep -oP '\d+(?=->3000)' | sort -n | tail -1 || echo "3000") APP_PORT=$((LAST_PORT + 1)) [ $APP_PORT -lt 3001 ] && APP_PORT=3001 DB_NAME="inbox_${COMPANY_SLUG//-/_}" INSTALL_DIR="/opt/inbox-${COMPANY_SLUG}" STACK_NAME="inbox-${COMPANY_SLUG}" DB_TYPE="${DETECTED_PG_TYPE:-postgresql}" echo "" echo -e "${GREEN}============================================${NC}" echo -e "${GREEN} RESUMO DA INSTALACAO ${NC}" echo -e "${GREEN}============================================${NC}" echo "" echo -e " Empresa: ${CYAN}$COMPANY_NAME${NC}" echo -e " App: ${CYAN}$APP_TITLE${NC}" echo -e " Dominio: ${CYAN}https://$DOMAIN${NC}" echo -e " Porta: ${CYAN}$APP_PORT${NC}" echo "" echo -e " Banco: ${CYAN}$DB_TYPE://$DB_USER@$DB_HOST:$DB_PORT/$DB_NAME${NC}" echo -e " Evolution: ${CYAN}$EVO_URL${NC}" echo -e " Instancia: ${CYAN}$EVO_INSTANCE${NC}" echo "" echo -e " Rede Docker: ${CYAN}$NETWORK_NAME${NC}" echo -e " Diretorio: ${CYAN}$INSTALL_DIR${NC}" echo -e " Stack Name: ${CYAN}$STACK_NAME${NC}" echo "" read -p "Confirmar instalacao? (S/n): " CONFIRM if [ "$CONFIRM" = "n" ] || [ "$CONFIRM" = "N" ]; then show_menu return fi # Verificar se ja existe if [ -d "$INSTALL_DIR" ]; then log_warning "Ja existe instalacao em $INSTALL_DIR" read -p "Sobrescrever? (s/N): " OVERWRITE if [ "$OVERWRITE" != "s" ] && [ "$OVERWRITE" != "S" ]; then show_menu return fi docker stack rm "$STACK_NAME" 2>/dev/null || true sleep 5 rm -rf "$INSTALL_DIR" fi # Garantir que a rede existe log_info "Verificando rede Docker..." ensure_network_exists "$NETWORK_NAME" log_info "Criando diretorio..." mkdir -p "$INSTALL_DIR" cd "$INSTALL_DIR" log_info "Baixando arquivos do Inbox ZoeSolutions..." if curl -sSL "${BASE_URL}/inbox/inbox-zoesolutions.tar.gz" -o inbox.tar.gz 2>/dev/null; then tar -xzf inbox.tar.gz 2>/dev/null || true rm -f inbox.tar.gz log_success "Arquivos baixados" else log_error "Erro ao baixar arquivos. Verifique a URL." exit 1 fi # Criar pasta patches se nao existir (para evitar erro no COPY) mkdir -p patches touch patches/.gitkeep JWT_SECRET=$(openssl rand -hex 32) log_info "Criando configuracao..." if [ "$DB_TYPE" = "mysql" ]; then DATABASE_URL="mysql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}" else DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}" fi cat > .env << EOF # Inbox ZoeSolutions - $COMPANY_NAME # Gerado em: $(date) COMPANY_NAME=$COMPANY_NAME COMPANY_SLUG=$COMPANY_SLUG DOMAIN=$DOMAIN APP_PORT=$APP_PORT DATABASE_URL=$DATABASE_URL JWT_SECRET=$JWT_SECRET OWNER_OPEN_ID=admin EVOLUTION_API_URL=$EVO_URL EVOLUTION_API_KEY=$EVO_KEY EVOLUTION_INSTANCE=$EVO_INSTANCE VITE_APP_ID=inbox-$COMPANY_SLUG VITE_APP_TITLE=$APP_TITLE NODE_ENV=production NETWORK_NAME=$NETWORK_NAME STACK_NAME=$STACK_NAME EOF log_info "Criando stack.yaml para Docker Swarm..." # Nome do servico para labels do Traefik (sem hifens para evitar problemas) SERVICE_LABEL=$(echo "$STACK_NAME" | tr '-' '_') cat > stack.yaml << EOF version: "3.8" services: app: image: ${STACK_NAME}:latest environment: - NODE_ENV=production - PORT=3000 - DATABASE_URL=${DATABASE_URL} - JWT_SECRET=${JWT_SECRET} - OWNER_OPEN_ID=admin - EVOLUTION_API_URL=${EVO_URL} - EVOLUTION_API_KEY=${EVO_KEY} - EVOLUTION_INSTANCE=${EVO_INSTANCE} - VITE_APP_ID=inbox-${COMPANY_SLUG} - VITE_APP_TITLE=${APP_TITLE} networks: - ${NETWORK_NAME} deploy: mode: replicated replicas: 1 placement: constraints: - node.role == manager labels: - traefik.enable=true - traefik.http.routers.${SERVICE_LABEL}.rule=Host(\`${DOMAIN}\`) - traefik.http.routers.${SERVICE_LABEL}.entrypoints=websecure - traefik.http.routers.${SERVICE_LABEL}.tls.certresolver=letsencryptresolver - traefik.http.services.${SERVICE_LABEL}.loadbalancer.server.port=3000 resources: limits: cpus: '1' memory: 512M reservations: cpus: '0.25' memory: 256M networks: ${NETWORK_NAME}: external: true EOF log_info "Criando Dockerfile..." cat > Dockerfile << 'DOCKERFILE' FROM node:22-alpine WORKDIR /app # Instalar dependencias de build RUN corepack enable && corepack prepare pnpm@latest --activate RUN apk add --no-cache python3 make g++ # Copiar arquivos de dependencias COPY package.json pnpm-lock.yaml ./ COPY patches ./patches # Instalar todas as dependencias RUN pnpm install --frozen-lockfile # Copiar codigo fonte COPY . . # Build da aplicacao RUN pnpm build # Copiar arquivos do frontend para o lugar correto RUN mkdir -p dist/public && cp -r client/dist/* dist/public/ # Criar usuario nodejs RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 RUN chown -R nodejs:nodejs /app USER nodejs EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 CMD ["node", "dist/index.js"] DOCKERFILE log_info "Criando banco de dados..." if [ "$DB_TYPE" = "mysql" ]; then docker exec -i ${DB_HOST} mysql -u${DB_USER} -p${DB_PASS} -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME};" 2>/dev/null && \ log_success "Banco $DB_NAME criado" || \ log_warning "Crie o banco manualmente: CREATE DATABASE $DB_NAME;" else if docker exec -i ${DB_HOST} psql -U $DB_USER -tc "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME'" 2>/dev/null | grep -q 1; then log_warning "Banco $DB_NAME ja existe" else docker exec -i ${DB_HOST} psql -U $DB_USER -c "CREATE DATABASE $DB_NAME" 2>/dev/null && \ log_success "Banco $DB_NAME criado" || \ log_warning "Crie o banco manualmente: CREATE DATABASE $DB_NAME;" fi fi echo "" log_info "Construindo imagem Docker (pode demorar alguns minutos)..." docker build -t ${STACK_NAME}:latest . 2>&1 | tail -20 if [ $? -ne 0 ]; then log_error "Erro ao construir imagem Docker" echo "" echo "Verifique os logs acima para identificar o problema." read -p "Pressione ENTER para voltar ao menu..." show_menu return fi log_success "Imagem construida com sucesso" log_info "Fazendo deploy da stack no Swarm..." docker stack deploy -c stack.yaml ${STACK_NAME} --with-registry-auth 2>&1 log_info "Aguardando inicializacao (30s)..." sleep 30 # Verificar status echo "" log_info "Verificando status da stack..." docker stack ps ${STACK_NAME} --format "table {{.Name}}\t{{.CurrentState}}\t{{.Error}}" 2>/dev/null || true if docker stack ps ${STACK_NAME} --format '{{.CurrentState}}' 2>/dev/null | grep -q "Running"; then echo "" echo -e "${GREEN}============================================${NC}" echo -e "${GREEN} INSTALACAO CONCLUIDA COM SUCESSO! ${NC}" echo -e "${GREEN}============================================${NC}" echo "" echo -e " ${CYAN}Empresa:${NC} $COMPANY_NAME" echo -e " ${CYAN}Acesse:${NC} https://$DOMAIN" echo -e " ${CYAN}Stack:${NC} $STACK_NAME" echo -e " ${CYAN}Diretorio:${NC} $INSTALL_DIR" echo "" echo -e "${YELLOW}IMPORTANTE - Configure o Webhook na Evolution API:${NC}" echo -e " URL: ${CYAN}https://$DOMAIN/api/webhook/evolution${NC}" echo "" echo -e "${YELLOW}Comandos uteis:${NC}" echo " docker stack ps $STACK_NAME # Ver status" echo " docker service logs ${STACK_NAME}_app -f # Ver logs" echo " docker stack rm $STACK_NAME # Remover stack" echo "" else log_warning "Stack iniciando... Verificando logs..." echo "" docker service logs ${STACK_NAME}_app --tail=30 2>/dev/null || true echo "" echo -e "${YELLOW}A stack pode demorar alguns minutos para iniciar completamente.${NC}" echo -e "${YELLOW}Verifique o status com: docker stack ps $STACK_NAME${NC}" echo "" fi echo "" read -p "Pressione ENTER para voltar ao menu..." show_menu } # ============================================ # INICIO # ============================================ check_infrastructure show_menu