Last active
September 9, 2025 13:54
-
-
Save diegslva/3250d8da91f65de91c0d2958d971eea7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # guistorm-manager.sh - Gerenciador AutomΓ‘tico de SubdomΓnios | |
| # Author: Diego L.S. - github.com/dieglsva | |
| # Data: 16/01/2025 | |
| # DescriΓ§Γ£o: Adiciona, remove e gerencia subdomΓnios no Caddy + Go Apps | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| set -e | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONFIGURAΓΓES GLOBAIS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| readonly CADDY_CONFIG="/etc/caddy/Caddyfile" | |
| readonly CADDY_BACKUP_DIR="/etc/caddy/backups" | |
| readonly APPS_DIR="/opt/apps" | |
| readonly SYSTEMD_DIR="/etc/systemd/system" | |
| readonly LOG_DIR="/var/log/guistorm" | |
| readonly DOMAIN_BASE="guistorm.io" | |
| readonly EMAIL="dieg.slva@gmail.com" | |
| # Cores para output | |
| readonly RED='\033[0;31m' | |
| readonly GREEN='\033[0;32m' | |
| readonly YELLOW='\033[1;33m' | |
| readonly BLUE='\033[0;34m' | |
| readonly CYAN='\033[0;36m' | |
| readonly NC='\033[0m' | |
| # PrΓ³xima porta disponΓvel (incrementa automaticamente) | |
| PORT_FILE="/etc/caddy/.next_port" | |
| [[ ! -f "$PORT_FILE" ]] && echo "8090" > "$PORT_FILE" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓES DE UTILIDADE | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| log_info() { echo -e "${GREEN}[β]${NC} $1"; } | |
| log_warn() { echo -e "${YELLOW}[β]${NC} $1"; } | |
| log_error() { echo -e "${RED}[β]${NC} $1"; } | |
| log_blue() { echo -e "${BLUE}[β]${NC} $1"; } | |
| log_cyan() { echo -e "${CYAN}[β ]${NC} $1"; } | |
| show_header() { | |
| echo -e "${CYAN}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
| echo -e "${CYAN} GUISTORM.IO - GERENCIADOR DE SUBDOMΓNIOS${NC}" | |
| echo -e "${CYAN}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
| echo "" | |
| } | |
| # Verificar se estΓ‘ rodando como root | |
| check_root() { | |
| if [[ $EUID -ne 0 ]]; then | |
| log_error "Este script precisa ser executado como root!" | |
| exit 1 | |
| fi | |
| } | |
| # Backup do Caddyfile | |
| backup_caddy() { | |
| mkdir -p "$CADDY_BACKUP_DIR" | |
| local backup_file="$CADDY_BACKUP_DIR/Caddyfile.$(date +%Y%m%d_%H%M%S).bak" | |
| cp "$CADDY_CONFIG" "$backup_file" | |
| log_info "Backup criado: $backup_file" | |
| } | |
| # Validar nome do subdomΓnio | |
| validate_subdomain() { | |
| local subdomain=$1 | |
| if [[ ! "$subdomain" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; then | |
| log_error "Nome de subdomΓnio invΓ‘lido! Use apenas: a-z, 0-9 e hΓfens" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Obter prΓ³xima porta disponΓvel | |
| get_next_port() { | |
| local current_port=$(cat "$PORT_FILE") | |
| echo "$current_port" | |
| echo $((current_port + 1)) > "$PORT_FILE" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # INICIALIZAR CADDYFILE COM SNIPPETS (se necessΓ‘rio) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| init_caddy_snippets() { | |
| if ! grep -q "(security_headers)" "$CADDY_CONFIG" 2>/dev/null; then | |
| log_warn "Inicializando snippets no Caddyfile..." | |
| # Criar backup do original | |
| backup_caddy | |
| # Adicionar snippets no inΓcio do arquivo | |
| cat > "$CADDY_CONFIG.tmp" << 'EOF' | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONFIGURAΓΓO GLOBAL - GUISTORM.IO | |
| # Gerenciado por: guistorm-manager.sh | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| { | |
| email dieg.slva@gmail.com | |
| servers { | |
| protocols h1 h2 h3 | |
| strict_sni_host on | |
| } | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SNIPPETS REUTILIZΓVEIS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Headers de seguranΓ§a padrΓ£o | |
| (security_headers) { | |
| header { | |
| Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" | |
| X-Content-Type-Options "nosniff" | |
| X-Frame-Options "SAMEORIGIN" | |
| X-XSS-Protection "1; mode=block" | |
| Referrer-Policy "strict-origin-when-cross-origin" | |
| -Server | |
| -X-Powered-By | |
| } | |
| } | |
| # CompressΓ£o otimizada | |
| (compression) { | |
| encode { | |
| gzip 9 | |
| zstd | |
| match { | |
| header Content-Type text/* | |
| header Content-Type application/json* | |
| header Content-Type application/javascript* | |
| header Content-Type application/xml* | |
| } | |
| } | |
| } | |
| # Rate limiting padrΓ£o | |
| (rate_limiting) { | |
| rate_limit { | |
| zone dynamic { | |
| key {remote_host} | |
| events 100 | |
| window 60s | |
| } | |
| } | |
| } | |
| # Logging padrΓ£o | |
| (logging) { | |
| log { | |
| output file /var/log/caddy/{args.0}.log { | |
| roll_size 50mb | |
| roll_keep 5 | |
| roll_keep_for 720h | |
| } | |
| format json | |
| } | |
| } | |
| # Proxy backend padrΓ£o | |
| (backend_proxy) { | |
| reverse_proxy localhost:{args.0} { | |
| health_uri /health | |
| health_interval 30s | |
| health_timeout 5s | |
| health_status 200 | |
| header_up X-Real-IP {remote_host} | |
| header_up X-Forwarded-For {remote_host} | |
| header_up X-Forwarded-Proto {scheme} | |
| header_up X-Forwarded-Host {host} | |
| transport http { | |
| dial_timeout 30s | |
| read_timeout 90s | |
| write_timeout 90s | |
| } | |
| } | |
| } | |
| # WebSocket support | |
| (websocket) { | |
| @websocket { | |
| header Connection *Upgrade* | |
| header Upgrade websocket | |
| } | |
| handle @websocket { | |
| reverse_proxy localhost:{args.0} { | |
| header_up Upgrade {http.request.header.Upgrade} | |
| header_up Connection "upgrade" | |
| } | |
| } | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # DOMΓNIOS CONFIGURADOS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| EOF | |
| # Adicionar configuraΓ§Γ΅es existentes | |
| if [[ -f "$CADDY_CONFIG" ]]; then | |
| # Extrair apenas as configuraΓ§Γ΅es de domΓnio (sem os headers globais) | |
| grep -A 1000 "^[a-z].*\.guistorm\.io {" "$CADDY_CONFIG" >> "$CADDY_CONFIG.tmp" 2>/dev/null || true | |
| fi | |
| mv "$CADDY_CONFIG.tmp" "$CADDY_CONFIG" | |
| log_info "Snippets inicializados com sucesso!" | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: ADICIONAR SUBDOMΓNIO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| add_subdomain() { | |
| local subdomain=$1 | |
| local port=${2:-$(get_next_port)} | |
| local type=${3:-"backend"} # backend, websocket, static | |
| # Validar entrada | |
| validate_subdomain "$subdomain" || return 1 | |
| # Verificar se jΓ‘ existe | |
| if grep -q "^$subdomain\.$DOMAIN_BASE {" "$CADDY_CONFIG" 2>/dev/null; then | |
| log_error "SubdomΓnio $subdomain.$DOMAIN_BASE jΓ‘ existe!" | |
| return 1 | |
| fi | |
| log_info "Adicionando subdomΓnio: $subdomain.$DOMAIN_BASE (porta: $port)" | |
| # Backup antes de modificar | |
| backup_caddy | |
| # Adicionar configuraΓ§Γ£o baseada no tipo | |
| case "$type" in | |
| "backend") | |
| cat >> "$CADDY_CONFIG" << EOF | |
| # $subdomain.$DOMAIN_BASE - Backend API | |
| # Adicionado em: $(date '+%Y-%m-%d %H:%M:%S') | |
| $subdomain.$DOMAIN_BASE { | |
| import security_headers | |
| import compression | |
| import rate_limiting | |
| import logging $subdomain | |
| import backend_proxy $port | |
| } | |
| EOF | |
| ;; | |
| "websocket") | |
| cat >> "$CADDY_CONFIG" << EOF | |
| # $subdomain.$DOMAIN_BASE - WebSocket Service | |
| # Adicionado em: $(date '+%Y-%m-%d %H:%M:%S') | |
| $subdomain.$DOMAIN_BASE { | |
| import security_headers | |
| import compression | |
| import rate_limiting | |
| import logging $subdomain | |
| import websocket $port | |
| import backend_proxy $port | |
| } | |
| EOF | |
| ;; | |
| "static") | |
| # Criar diretΓ³rio para arquivos estΓ‘ticos | |
| local static_dir="/var/www/$subdomain" | |
| mkdir -p "$static_dir" | |
| chown -R www-data:www-data "$static_dir" | |
| cat >> "$CADDY_CONFIG" << EOF | |
| # $subdomain.$DOMAIN_BASE - Static Website | |
| # Adicionado em: $(date '+%Y-%m-%d %H:%M:%S') | |
| $subdomain.$DOMAIN_BASE { | |
| import security_headers | |
| import compression | |
| import logging $subdomain | |
| root * /var/www/$subdomain | |
| try_files {path} /index.html | |
| file_server | |
| } | |
| EOF | |
| # Criar index.html de exemplo | |
| cat > "$static_dir/index.html" << EOF | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>$subdomain.$DOMAIN_BASE</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, system-ui, sans-serif; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 100vh; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| margin: 0; | |
| } | |
| .container { | |
| text-align: center; | |
| padding: 40px; | |
| } | |
| h1 { font-size: 3em; margin-bottom: 10px; } | |
| p { font-size: 1.2em; opacity: 0.9; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>π $subdomain.$DOMAIN_BASE</h1> | |
| <p>Site configurado com sucesso!</p> | |
| <p><small>Criado em: $(date '+%Y-%m-%d %H:%M:%S')</small></p> | |
| </div> | |
| </body> | |
| </html> | |
| EOF | |
| ;; | |
| esac | |
| # Criar aplicaΓ§Γ£o Go se for backend/websocket | |
| if [[ "$type" != "static" ]]; then | |
| create_go_app "$subdomain" "$port" "$type" | |
| fi | |
| # Validar e recarregar Caddy | |
| if caddy validate --config "$CADDY_CONFIG" &>/dev/null; then | |
| systemctl reload caddy | |
| log_info "Caddy recarregado com sucesso!" | |
| echo "" | |
| log_cyan "SubdomΓnio adicionado com sucesso!" | |
| echo -e "${GREEN}URL:${NC} https://$subdomain.$DOMAIN_BASE" | |
| echo -e "${GREEN}Porta:${NC} $port" | |
| echo -e "${GREEN}Tipo:${NC} $type" | |
| if [[ "$type" != "static" ]]; then | |
| echo -e "${GREEN}ServiΓ§o:${NC} $subdomain-app.service" | |
| echo "" | |
| echo -e "${YELLOW}Comandos ΓΊteis:${NC}" | |
| echo " β’ Status: systemctl status $subdomain-app" | |
| echo " β’ Logs: journalctl -u $subdomain-app -f" | |
| echo " β’ Editar: nano $APPS_DIR/$subdomain/main.go" | |
| else | |
| echo -e "${GREEN}DiretΓ³rio:${NC} /var/www/$subdomain" | |
| fi | |
| else | |
| log_error "Erro na configuraΓ§Γ£o do Caddy! Revertendo..." | |
| cp "$CADDY_BACKUP_DIR/$(ls -t $CADDY_BACKUP_DIR | head -1)" "$CADDY_CONFIG" | |
| return 1 | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: CRIAR APLICAΓΓO GO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| create_go_app() { | |
| local subdomain=$1 | |
| local port=$2 | |
| local type=$3 | |
| local app_dir="$APPS_DIR/$subdomain" | |
| log_info "Criando aplicaΓ§Γ£o Go para $subdomain..." | |
| mkdir -p "$app_dir" | |
| # Criar aplicaΓ§Γ£o baseada no tipo | |
| if [[ "$type" == "websocket" ]]; then | |
| cat > "$app_dir/main.go" << EOF | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "log" | |
| "net/http" | |
| "time" | |
| "github.com/gorilla/websocket" | |
| ) | |
| var upgrader = websocket.Upgrader{ | |
| CheckOrigin: func(r *http.Request) bool { | |
| return true // Ajustar em produΓ§Γ£o | |
| }, | |
| } | |
| type Message struct { | |
| Type string \`json:"type"\` | |
| Content string \`json:"content"\` | |
| Timestamp time.Time \`json:"timestamp"\` | |
| } | |
| func handleWebSocket(w http.ResponseWriter, r *http.Request) { | |
| conn, err := upgrader.Upgrade(w, r, nil) | |
| if err != nil { | |
| log.Printf("Erro no upgrade: %v", err) | |
| return | |
| } | |
| defer conn.Close() | |
| log.Printf("Cliente conectado: %s", r.RemoteAddr) | |
| // Echo server exemplo | |
| for { | |
| var msg Message | |
| err := conn.ReadJSON(&msg) | |
| if err != nil { | |
| log.Printf("Cliente desconectado: %v", err) | |
| break | |
| } | |
| msg.Timestamp = time.Now() | |
| msg.Type = "echo" | |
| if err := conn.WriteJSON(msg); err != nil { | |
| log.Printf("Erro ao enviar: %v", err) | |
| break | |
| } | |
| } | |
| } | |
| func handleHome(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "text/html") | |
| fmt.Fprintf(w, \` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>$subdomain WebSocket</title> | |
| </head> | |
| <body> | |
| <h1>WebSocket Service: $subdomain.$DOMAIN_BASE</h1> | |
| <p>Conecte via: wss://$subdomain.$DOMAIN_BASE/ws</p> | |
| </body> | |
| </html> | |
| \`) | |
| } | |
| func handleHealth(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(map[string]string{ | |
| "status": "healthy", | |
| "service": "$subdomain", | |
| "type": "websocket", | |
| }) | |
| } | |
| func main() { | |
| http.HandleFunc("/", handleHome) | |
| http.HandleFunc("/ws", handleWebSocket) | |
| http.HandleFunc("/health", handleHealth) | |
| log.Printf("[β] $subdomain WebSocket rodando na porta $port") | |
| log.Fatal(http.ListenAndServe(":$port", nil)) | |
| } | |
| EOF | |
| else | |
| # Backend API padrΓ£o | |
| cat > "$app_dir/main.go" << EOF | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "log" | |
| "net/http" | |
| "runtime" | |
| "time" | |
| ) | |
| type Response struct { | |
| Service string \`json:"service"\` | |
| Subdomain string \`json:"subdomain"\` | |
| Status string \`json:"status"\` | |
| Timestamp time.Time \`json:"timestamp"\` | |
| Version string \`json:"version"\` | |
| GoVersion string \`json:"go_version"\` | |
| Uptime string \`json:"uptime"\` | |
| } | |
| var startTime = time.Now() | |
| func handleRoot(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "application/json") | |
| response := Response{ | |
| Service: "$subdomain-service", | |
| Subdomain: "$subdomain.$DOMAIN_BASE", | |
| Status: "operational", | |
| Timestamp: time.Now(), | |
| Version: "1.0.0", | |
| GoVersion: runtime.Version(), | |
| Uptime: time.Since(startTime).String(), | |
| } | |
| json.NewEncoder(w).Encode(response) | |
| } | |
| func handleHealth(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(map[string]interface{}{ | |
| "status": "healthy", | |
| "uptime": time.Since(startTime).Seconds(), | |
| }) | |
| } | |
| func handleInfo(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "text/html; charset=utf-8") | |
| fmt.Fprintf(w, \` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>$subdomain.$DOMAIN_BASE</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, system-ui, sans-serif; | |
| max-width: 800px; | |
| margin: 100px auto; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| h1 { font-size: 2.5em; } | |
| .info { | |
| background: rgba(255,255,255,0.1); | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin-top: 20px; | |
| } | |
| code { | |
| background: rgba(0,0,0,0.3); | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>π $subdomain Service</h1> | |
| <div class="info"> | |
| <p><strong>DomΓnio:</strong> $subdomain.$DOMAIN_BASE</p> | |
| <p><strong>Status:</strong> β Operacional</p> | |
| <p><strong>API Endpoint:</strong> <code>GET /api</code></p> | |
| <p><strong>Health Check:</strong> <code>GET /health</code></p> | |
| <p><strong>Go Version:</strong> %s</p> | |
| <p><strong>Uptime:</strong> %s</p> | |
| </div> | |
| </body> | |
| </html> | |
| \`, runtime.Version(), time.Since(startTime)) | |
| } | |
| func main() { | |
| // Rotas | |
| http.HandleFunc("/", handleRoot) | |
| http.HandleFunc("/api", handleRoot) | |
| http.HandleFunc("/health", handleHealth) | |
| http.HandleFunc("/info", handleInfo) | |
| // Middleware de logging | |
| handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| start := time.Now() | |
| http.DefaultServeMux.ServeHTTP(w, r) | |
| log.Printf("%s %s %s %v", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start)) | |
| }) | |
| log.Printf("[β] $subdomain service rodando na porta $port") | |
| log.Printf("[β] URL: https://$subdomain.$DOMAIN_BASE") | |
| log.Fatal(http.ListenAndServe(":$port", handler)) | |
| } | |
| EOF | |
| fi | |
| # Compilar aplicaΓ§Γ£o | |
| cd "$app_dir" | |
| go mod init "$subdomain" 2>/dev/null || true | |
| if [[ "$type" == "websocket" ]]; then | |
| go get github.com/gorilla/websocket 2>/dev/null || true | |
| fi | |
| go build -o app main.go | |
| # Criar systemd service | |
| cat > "$SYSTEMD_DIR/$subdomain-app.service" << EOF | |
| [Unit] | |
| Description=$subdomain Service - $type | |
| After=network.target | |
| [Service] | |
| Type=simple | |
| User=www-data | |
| Group=www-data | |
| WorkingDirectory=$app_dir | |
| ExecStart=$app_dir/app | |
| Restart=always | |
| RestartSec=10 | |
| # Limites | |
| LimitNOFILE=65536 | |
| LimitNPROC=4096 | |
| # VariΓ‘veis de ambiente | |
| Environment="GO_ENV=production" | |
| Environment="SERVICE_NAME=$subdomain" | |
| Environment="PORT=$port" | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| # Iniciar serviΓ§o | |
| systemctl daemon-reload | |
| systemctl enable "$subdomain-app.service" | |
| systemctl start "$subdomain-app.service" | |
| log_info "AplicaΓ§Γ£o Go criada e iniciada!" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: REMOVER SUBDOMΓNIO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| remove_subdomain() { | |
| local subdomain=$1 | |
| if [[ -z "$subdomain" ]]; then | |
| log_error "Por favor, especifique o subdomΓnio a remover" | |
| return 1 | |
| fi | |
| # Verificar se existe | |
| if ! grep -q "^$subdomain\.$DOMAIN_BASE {" "$CADDY_CONFIG"; then | |
| log_error "SubdomΓnio $subdomain.$DOMAIN_BASE nΓ£o encontrado!" | |
| return 1 | |
| fi | |
| log_warn "Removendo subdomΓnio: $subdomain.$DOMAIN_BASE" | |
| # Confirmar | |
| echo -e "${YELLOW}β οΈ Esta aΓ§Γ£o irΓ‘:${NC}" | |
| echo " β’ Remover configuraΓ§Γ£o do Caddy" | |
| echo " β’ Parar e remover o serviΓ§o (se existir)" | |
| echo " β’ Remover aplicaΓ§Γ£o em $APPS_DIR/$subdomain" | |
| echo " β’ Remover diretΓ³rio web em /var/www/$subdomain (se existir)" | |
| echo "" | |
| read -p "Confirmar remoΓ§Γ£o? (s/N): " confirm | |
| if [[ "$confirm" != "s" ]]; then | |
| log_info "OperaΓ§Γ£o cancelada" | |
| return 0 | |
| fi | |
| # Backup antes de remover | |
| backup_caddy | |
| # Remover do Caddyfile (remove o bloco inteiro) | |
| sed -i "/^# $subdomain\.$DOMAIN_BASE/,/^$/d" "$CADDY_CONFIG" | |
| sed -i "/^$subdomain\.$DOMAIN_BASE {/,/^}/d" "$CADDY_CONFIG" | |
| # Parar e remover serviΓ§o se existir | |
| if systemctl list-units --all | grep -q "$subdomain-app.service"; then | |
| systemctl stop "$subdomain-app.service" 2>/dev/null || true | |
| systemctl disable "$subdomain-app.service" 2>/dev/null || true | |
| rm -f "$SYSTEMD_DIR/$subdomain-app.service" | |
| systemctl daemon-reload | |
| log_info "ServiΓ§o removido" | |
| fi | |
| # Remover aplicaΓ§Γ£o | |
| if [[ -d "$APPS_DIR/$subdomain" ]]; then | |
| rm -rf "$APPS_DIR/$subdomain" | |
| log_info "AplicaΓ§Γ£o removida" | |
| fi | |
| # Remover diretΓ³rio web se existir | |
| if [[ -d "/var/www/$subdomain" ]]; then | |
| rm -rf "/var/www/$subdomain" | |
| log_info "DiretΓ³rio web removido" | |
| fi | |
| # Recarregar Caddy | |
| systemctl reload caddy | |
| log_info "SubdomΓnio $subdomain.$DOMAIN_BASE removido com sucesso!" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: LISTAR SUBDOMΓNIOS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| list_subdomains() { | |
| log_cyan "SubdomΓnios configurados:" | |
| echo "" | |
| # Extrair subdomΓnios do Caddyfile | |
| local subdomains=$(grep -E "^[a-z0-9-]+\.guistorm\.io {" "$CADDY_CONFIG" | sed 's/ {//' | sort) | |
| if [[ -z "$subdomains" ]]; then | |
| log_warn "Nenhum subdomΓnio configurado" | |
| return | |
| fi | |
| printf "%-40s %-10s %-15s\n" "DOMΓNIO" "PORTA" "STATUS" | |
| printf "%-40s %-10s %-15s\n" "-------" "-----" "------" | |
| while IFS= read -r domain; do | |
| # Extrair porta do reverse_proxy | |
| local port=$(grep -A 10 "^$domain {" "$CADDY_CONFIG" | grep -oP 'localhost:\K\d+' | head -1) | |
| # Verificar status do serviΓ§o | |
| local service_name=$(echo "$domain" | cut -d'.' -f1) | |
| local status="N/A" | |
| if [[ -d "/var/www/$service_name" ]]; then | |
| status="Static" | |
| elif systemctl is-active "$service_name-app" &>/dev/null; then | |
| status="β Running" | |
| elif systemctl list-units --all | grep -q "$service_name-app.service"; then | |
| status="β Stopped" | |
| else | |
| status="π No service" | |
| fi | |
| printf "%-40s %-10s %-15s\n" "$domain" "${port:-static}" "$status" | |
| done <<< "$subdomains" | |
| echo "" | |
| # Mostrar estatΓsticas | |
| local total=$(echo "$subdomains" | wc -l) | |
| local running=$(systemctl list-units --state=running | grep -c "app.service" || echo 0) | |
| echo -e "${CYAN}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
| echo -e "Total: $total | Rodando: $running | PrΓ³xima porta: $(cat $PORT_FILE)" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: STATUS DETALHADO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| show_status() { | |
| local subdomain=$1 | |
| if [[ -z "$subdomain" ]]; then | |
| # Status geral | |
| log_cyan "Status Geral do Sistema" | |
| echo "" | |
| echo -e "${YELLOW}[β] Caddy:${NC}" | |
| systemctl status caddy --no-pager | head -5 | |
| echo "" | |
| echo -e "${YELLOW}[β] Certificados SSL:${NC}" | |
| caddy list-certificates 2>/dev/null | grep "guistorm.io" || echo " Nenhum certificado ativo" | |
| echo "" | |
| echo -e "${YELLOW}[β] Recursos:${NC}" | |
| free -h | grep Mem | awk '{print " MemΓ³ria: " $3 " / " $2}' | |
| df -h / | tail -1 | awk '{print " Disco: " $3 " / " $2}' | |
| echo "" | |
| list_subdomains | |
| else | |
| # Status especΓfico | |
| if ! grep -q "^$subdomain\.$DOMAIN_BASE {" "$CADDY_CONFIG"; then | |
| log_error "SubdomΓnio $subdomain.$DOMAIN_BASE nΓ£o encontrado!" | |
| return 1 | |
| fi | |
| log_cyan "Status: $subdomain.$DOMAIN_BASE" | |
| echo "" | |
| # ConfiguraΓ§Γ£o Caddy | |
| echo -e "${YELLOW}[β] ConfiguraΓ§Γ£o Caddy:${NC}" | |
| grep -A 8 "^$subdomain\.$DOMAIN_BASE {" "$CADDY_CONFIG" | |
| # ServiΓ§o | |
| if systemctl list-units --all | grep -q "$subdomain-app.service"; then | |
| echo "" | |
| echo -e "${YELLOW}[β] ServiΓ§o:${NC}" | |
| systemctl status "$subdomain-app.service" --no-pager | head -10 | |
| echo "" | |
| echo -e "${YELLOW}[β] Γltimos logs:${NC}" | |
| journalctl -u "$subdomain-app.service" -n 5 --no-pager | |
| fi | |
| # Teste de conectividade | |
| echo "" | |
| echo -e "${YELLOW}[β] Teste de conectividade:${NC}" | |
| local port=$(grep -A 10 "^$subdomain\.$DOMAIN_BASE {" "$CADDY_CONFIG" | grep -oP 'localhost:\K\d+' | head -1) | |
| if [[ -n "$port" ]]; then | |
| if curl -s "http://localhost:$port/health" > /dev/null 2>&1; then | |
| echo -e " ${GREEN}β Health check OK${NC}" | |
| else | |
| echo -e " ${RED}β Health check falhou${NC}" | |
| fi | |
| fi | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: EDITAR APLICAΓΓO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| edit_app() { | |
| local subdomain=$1 | |
| if [[ -z "$subdomain" ]]; then | |
| log_error "Por favor, especifique o subdomΓnio" | |
| return 1 | |
| fi | |
| local app_file="$APPS_DIR/$subdomain/main.go" | |
| if [[ ! -f "$app_file" ]]; then | |
| log_error "AplicaΓ§Γ£o nΓ£o encontrada: $app_file" | |
| return 1 | |
| fi | |
| log_info "Editando: $app_file" | |
| nano "$app_file" | |
| echo "" | |
| read -p "Recompilar e reiniciar o serviΓ§o? (s/N): " confirm | |
| if [[ "$confirm" == "s" ]]; then | |
| cd "$APPS_DIR/$subdomain" | |
| go build -o app main.go | |
| systemctl restart "$subdomain-app.service" | |
| log_info "ServiΓ§o reiniciado!" | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: MENU INTERATIVO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| show_menu() { | |
| clear | |
| show_header | |
| echo -e "${CYAN}Escolha uma opΓ§Γ£o:${NC}" | |
| echo "" | |
| echo " 1) π Adicionar novo subdomΓnio" | |
| echo " 2) ποΈ Remover subdomΓnio" | |
| echo " 3) π Listar subdomΓnios" | |
| echo " 4) π Status detalhado" | |
| echo " 5) βοΈ Editar aplicaΓ§Γ£o" | |
| echo " 6) π Recarregar Caddy" | |
| echo " 7) π Ver logs do Caddy" | |
| echo " 8) π§ Validar configuraΓ§Γ£o" | |
| echo " 9) π¦ Backup completo" | |
| echo " 0) πͺ Sair" | |
| echo "" | |
| echo -e "${CYAN}βββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
| read -p "OpΓ§Γ£o: " choice | |
| case $choice in | |
| 1) | |
| read -p "Nome do subdomΓnio (ex: api): " subdomain | |
| echo "Tipo de serviΓ§o:" | |
| echo " 1) Backend API (padrΓ£o)" | |
| echo " 2) WebSocket" | |
| echo " 3) Site estΓ‘tico" | |
| read -p "Escolha (1-3): " type_choice | |
| case $type_choice in | |
| 2) type="websocket" ;; | |
| 3) type="static" ;; | |
| *) type="backend" ;; | |
| esac | |
| add_subdomain "$subdomain" "" "$type" | |
| ;; | |
| 2) | |
| list_subdomains | |
| echo "" | |
| read -p "Nome do subdomΓnio para remover: " subdomain | |
| remove_subdomain "$subdomain" | |
| ;; | |
| 3) | |
| list_subdomains | |
| ;; | |
| 4) | |
| read -p "SubdomΓnio especΓfico (ou Enter para geral): " subdomain | |
| show_status "$subdomain" | |
| ;; | |
| 5) | |
| list_subdomains | |
| echo "" | |
| read -p "Nome do subdomΓnio para editar: " subdomain | |
| edit_app "$subdomain" | |
| ;; | |
| 6) | |
| systemctl reload caddy | |
| log_info "Caddy recarregado!" | |
| ;; | |
| 7) | |
| journalctl -u caddy -f | |
| ;; | |
| 8) | |
| if caddy validate --config "$CADDY_CONFIG"; then | |
| log_info "ConfiguraΓ§Γ£o vΓ‘lida!" | |
| else | |
| log_error "ConfiguraΓ§Γ£o invΓ‘lida!" | |
| fi | |
| ;; | |
| 9) | |
| backup_caddy | |
| tar -czf "/root/guistorm-backup-$(date +%Y%m%d_%H%M%S).tar.gz" \ | |
| "$CADDY_CONFIG" "$APPS_DIR" "$SYSTEMD_DIR"/*-app.service 2>/dev/null | |
| log_info "Backup completo criado em /root/" | |
| ;; | |
| 0) | |
| echo "" | |
| log_info "AtΓ© logo!" | |
| exit 0 | |
| ;; | |
| *) | |
| log_error "OpΓ§Γ£o invΓ‘lida!" | |
| ;; | |
| esac | |
| echo "" | |
| read -p "Pressione Enter para continuar..." | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: MODO CLI (Linha de Comando) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| handle_cli() { | |
| case "$1" in | |
| add) | |
| shift | |
| add_subdomain "$@" | |
| ;; | |
| remove|rm) | |
| shift | |
| remove_subdomain "$@" | |
| ;; | |
| list|ls) | |
| list_subdomains | |
| ;; | |
| status) | |
| shift | |
| show_status "$@" | |
| ;; | |
| edit) | |
| shift | |
| edit_app "$@" | |
| ;; | |
| reload) | |
| systemctl reload caddy | |
| log_info "Caddy recarregado!" | |
| ;; | |
| validate) | |
| caddy validate --config "$CADDY_CONFIG" | |
| ;; | |
| help|--help|-h) | |
| show_help | |
| ;; | |
| *) | |
| log_error "Comando invΓ‘lido: $1" | |
| show_help | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FUNΓΓO: AJUDA | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| show_help() { | |
| echo "Uso: $0 [comando] [opΓ§Γ΅es]" | |
| echo "" | |
| echo "Comandos:" | |
| echo " add <nome> [porta] [tipo] - Adicionar subdomΓnio" | |
| echo " remove <nome> - Remover subdomΓnio" | |
| echo " list - Listar subdomΓnios" | |
| echo " status [nome] - Mostrar status" | |
| echo " edit <nome> - Editar aplicaΓ§Γ£o" | |
| echo " reload - Recarregar Caddy" | |
| echo " validate - Validar configuraΓ§Γ£o" | |
| echo " help - Mostrar esta ajuda" | |
| echo "" | |
| echo "Tipos de serviΓ§o: backend (padrΓ£o), websocket, static" | |
| echo "" | |
| echo "Exemplos:" | |
| echo " $0 add api # Adiciona api.guistorm.io" | |
| echo " $0 add chat 8090 websocket # Adiciona chat com WebSocket" | |
| echo " $0 add docs 0 static # Adiciona site estΓ‘tico" | |
| echo " $0 remove api # Remove api.guistorm.io" | |
| echo " $0 list # Lista todos os subdomΓnios" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| main() { | |
| # Verificar root | |
| check_root | |
| # Inicializar snippets se necessΓ‘rio | |
| init_caddy_snippets | |
| # Criar diretΓ³rios necessΓ‘rios | |
| mkdir -p "$APPS_DIR" "$LOG_DIR" "$CADDY_BACKUP_DIR" | |
| # Modo de execuΓ§Γ£o | |
| if [[ $# -eq 0 ]]; then | |
| # Menu interativo | |
| while true; do | |
| show_menu | |
| done | |
| else | |
| # Modo CLI | |
| handle_cli "$@" | |
| fi | |
| } | |
| # Executar | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment