Files
workshop/aula-09/add-client.sh
Allyson de Paula 07b7ee62d3 Workshop completo: aulas 08-10 com Talos, n8n e GitLab na Hetzner
Aula 08 - Cluster Kubernetes HA:
- Setup interativo com OpenTofu para Talos na Hetzner
- CCM, CSI Driver, Cluster Autoscaler, Metrics Server
- NGINX Ingress com LoadBalancer (HTTP/HTTPS/SSH)

Aula 09 - n8n na Hetzner:
- Deploy via Helm com PostgreSQL e Redis
- Suporte multi-tenant com add-client.sh
- Integração com Hetzner CSI para volumes persistentes

Aula 10 - GitLab na Hetzner:
- Setup agnóstico: CloudFlare (trusted proxies) ou Let's Encrypt
- Anti-affinity para distribuir webservice/sidekiq em nós diferentes
- Container Registry e SSH via TCP passthrough
- Documentação do erro 422 e solução com trustedCIDRsForXForwardedFor

Melhorias gerais:
- READMEs atualizados com arquitetura e troubleshooting
- Scripts cleanup.sh para todas as aulas
- CLAUDE.md atualizado com contexto do projeto
2025-12-31 17:57:02 -03:00

300 lines
10 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# Script para Adicionar Novo Cliente
# =============================================================================
#
# Este script provisiona um novo cliente com:
# - n8n em namespace separado (cliente-n8n)
# - Grupo no GitLab compartilhado (opcional)
#
# Uso:
# ./add-client.sh <nome-cliente>
#
# Exemplo:
# ./add-client.sh acme
# -> Cria namespace acme-n8n com n8n
# -> Cria grupo /acme no GitLab (se disponível)
#
# Pré-requisitos:
# - ./setup.sh já executado (gera .env com configurações)
# - kubectl configurado
# - Helm instalado
#
# =============================================================================
set -e
# Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERRO]${NC} $1"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT=$1
# =============================================================================
# CARREGAR CONFIGURAÇÃO
# =============================================================================
if [[ ! -f "$SCRIPT_DIR/.env" ]]; then
log_error "Arquivo .env não encontrado!"
log_error "Execute ./setup.sh primeiro para configurar o ambiente."
exit 1
fi
source "$SCRIPT_DIR/.env"
# Suporte ao novo formato (N8N_HOST) e legado (DOMAIN)
if [[ -n "$N8N_HOST" && -z "$DOMAIN" ]]; then
# Extrair DOMAIN do N8N_HOST (ex: n8n.kube.quest → kube.quest)
DOMAIN=$(echo "$N8N_HOST" | sed 's/^[^.]*\.//')
fi
if [[ -z "$DOMAIN" ]]; then
log_error "Variável DOMAIN ou N8N_HOST não definida no .env"
log_error "Execute ./setup.sh novamente."
exit 1
fi
# Carregar configuração do GitLab se disponível
GITLAB_HOST=""
if [[ -f "$SCRIPT_DIR/../aula-10/.env" ]]; then
source "$SCRIPT_DIR/../aula-10/.env"
fi
# =============================================================================
# VALIDAÇÕES
# =============================================================================
if [ -z "$CLIENT" ]; then
echo "Uso: $0 <nome-cliente>"
echo ""
echo "Exemplo:"
echo " $0 acme"
echo ""
echo "Configuração atual (de .env):"
echo " Domínio: ${DOMAIN}"
echo " CloudFlare: ${USE_CLOUDFLARE}"
echo " Let's Encrypt: ${USE_LETSENCRYPT}"
echo ""
echo "Isso vai criar:"
echo " - Namespace: acme-n8n"
PROTOCOL="https"
[[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]] && PROTOCOL="http"
echo " - n8n em: ${PROTOCOL}://acme-n8n.${DOMAIN}"
if [[ -n "$GITLAB_HOST" ]]; then
echo " - GitLab grupo: https://${GITLAB_HOST}/acme/ (se configurado)"
fi
exit 1
fi
# Validar nome do cliente (só letras minúsculas, números e hífens)
if ! [[ "$CLIENT" =~ ^[a-z0-9-]+$ ]]; then
log_error "Nome do cliente inválido: $CLIENT"
log_error "Use apenas letras minúsculas, números e hífens"
exit 1
fi
log_info "Verificando pré-requisitos..."
if ! command -v kubectl &> /dev/null; then
log_error "kubectl não encontrado"
exit 1
fi
if ! command -v helm &> /dev/null; then
log_error "Helm não encontrado"
exit 1
fi
if ! kubectl cluster-info &> /dev/null; then
log_error "Não foi possível conectar ao cluster"
exit 1
fi
log_success "Pré-requisitos OK"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Adicionando Cliente: $CLIENT${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo ""
# =============================================================================
# 1. CRIAR NAMESPACE PARA N8N
# =============================================================================
log_info "Criando namespace ${CLIENT}-n8n..."
kubectl create namespace ${CLIENT}-n8n --dry-run=client -o yaml | kubectl apply -f -
log_success "Namespace criado"
# =============================================================================
# 2. INSTALAR N8N
# =============================================================================
log_info "Instalando n8n para ${CLIENT}..."
# Verificar se base-n8n-values.yaml existe
if [ ! -f "$SCRIPT_DIR/base-n8n-values.yaml" ]; then
log_error "Arquivo base-n8n-values.yaml não encontrado em $SCRIPT_DIR"
exit 1
fi
# Adicionar repo se necessário
helm repo add community-charts https://community-charts.github.io/helm-charts 2>/dev/null || true
helm repo update
# Construir argumentos do Helm dinamicamente
HELM_ARGS=""
# Configurar host do ingress
HELM_ARGS="$HELM_ARGS --set ingress.enabled=true"
HELM_ARGS="$HELM_ARGS --set ingress.className=nginx"
HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].host=${CLIENT}-n8n.${DOMAIN}"
HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].path=/"
HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].pathType=Prefix"
# Configurar webhook URL
if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then
HELM_ARGS="$HELM_ARGS --set webhook.url=https://${CLIENT}-n8n.${DOMAIN}"
else
HELM_ARGS="$HELM_ARGS --set webhook.url=http://${CLIENT}-n8n.${DOMAIN}"
fi
# Configurar TLS (se Let's Encrypt)
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
HELM_ARGS="$HELM_ARGS --set ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt"
HELM_ARGS="$HELM_ARGS --set ingress.tls[0].hosts[0]=${CLIENT}-n8n.${DOMAIN}"
HELM_ARGS="$HELM_ARGS --set ingress.tls[0].secretName=${CLIENT}-n8n-tls"
fi
# Configurar N8N_SECURE_COOKIE (se HTTPS)
if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then
HELM_ARGS="$HELM_ARGS --set main.extraEnvVars.N8N_SECURE_COOKIE=true"
HELM_ARGS="$HELM_ARGS --set worker.extraEnvVars.N8N_SECURE_COOKIE=true"
HELM_ARGS="$HELM_ARGS --set webhook.extraEnvVars.N8N_SECURE_COOKIE=true"
fi
# Instalar n8n
eval helm upgrade --install ${CLIENT}-n8n community-charts/n8n \
--namespace ${CLIENT}-n8n \
-f "$SCRIPT_DIR/base-n8n-values.yaml" \
$HELM_ARGS \
--wait \
--timeout 5m
log_success "n8n instalado"
# =============================================================================
# 3. CRIAR GRUPO NO GITLAB (opcional)
# =============================================================================
log_info "Verificando GitLab..."
# Obter token do GitLab
if [ -z "$GITLAB_TOKEN" ]; then
GITLAB_TOKEN=$(kubectl get secret gitlab-gitlab-initial-root-password \
-n gitlab \
-o jsonpath='{.data.password}' 2>/dev/null | base64 -d || echo "")
fi
if [ -z "$GITLAB_TOKEN" ]; then
log_warn "GitLab não encontrado ou não configurado"
log_warn "Pule esta etapa ou crie o grupo manualmente"
else
log_info "Criando grupo no GitLab..."
# Usar GITLAB_HOST se disponível (da aula-10/.env)
if [[ -n "$GITLAB_HOST" ]]; then
GITLAB_URL="https://${GITLAB_HOST}"
else
GITLAB_URL="https://git.${DOMAIN}"
fi
# Criar grupo via API
RESULT=$(curl -s --request POST "${GITLAB_URL}/api/v4/groups" \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
--form "name=${CLIENT}" \
--form "path=${CLIENT}" \
--form "visibility=private" \
--write-out "%{http_code}" \
--output /tmp/gitlab-group-result.json 2>/dev/null || echo "000")
if [ "$RESULT" == "201" ]; then
log_success "Grupo criado no GitLab"
elif [ "$RESULT" == "400" ]; then
log_warn "Grupo já existe ou erro de validação"
else
log_warn "Não foi possível criar grupo (HTTP $RESULT)"
log_warn "Crie manualmente em ${GITLAB_URL}/admin/groups/new"
fi
fi
# =============================================================================
# RESUMO
# =============================================================================
# Obter IP do LoadBalancer
LB_IP=$(kubectl get svc ingress-nginx-controller \
-n ingress-nginx \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "<pendente>")
# Determinar protocolo
PROTOCOL="https"
[[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]] && PROTOCOL="http"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Cliente $CLIENT Provisionado!${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo ""
echo "Serviços:"
echo -e " n8n: ${GREEN}${PROTOCOL}://${CLIENT}-n8n.${DOMAIN}${NC}"
if [ -n "$GITLAB_TOKEN" ]; then
if [[ -n "$GITLAB_HOST" ]]; then
echo -e " GitLab: ${GREEN}https://${GITLAB_HOST}/${CLIENT}/${NC}"
else
echo -e " GitLab: ${GREEN}https://git.${DOMAIN}/${CLIENT}/${NC}"
fi
fi
echo ""
if [[ "$USE_CLOUDFLARE" == "true" ]]; then
echo "Configure no CloudFlare:"
echo -e " ${YELLOW}Tipo:${NC} A"
echo -e " ${YELLOW}Nome:${NC} ${CLIENT}-n8n"
echo -e " ${YELLOW}Conteúdo:${NC} ${GREEN}${LB_IP}${NC}"
echo -e " ${YELLOW}Proxy:${NC} ✓ (ícone laranja)"
echo ""
echo "(Ou use o wildcard *.${DOMAIN} se já configurado)"
else
echo "Configure o DNS:"
echo -e " ${YELLOW}Tipo:${NC} A"
echo -e " ${YELLOW}Nome:${NC} ${CLIENT}-n8n"
echo -e " ${YELLOW}Valor:${NC} ${GREEN}${LB_IP}${NC}"
echo ""
echo "(Ou use o wildcard *.${DOMAIN} se já configurado)"
fi
if [ -n "$GITLAB_TOKEN" ]; then
echo ""
echo "Git clone:"
if [[ -n "$GITLAB_HOST" ]]; then
echo " git clone git@${GITLAB_HOST}:${CLIENT}/meu-projeto.git"
else
echo " git clone git@git.${DOMAIN}:${CLIENT}/meu-projeto.git"
fi
fi
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"