#!/usr/bin/env bash set -Eeuo pipefail # ============================================================ # AD TRACK • LINCE DIGITAL # Docker + Compose + Portainer CE + Nginx Proxy Manager # BY LUCAS SANTANA # ============================================================ PORTAINER_DIR="/opt/portainer" NPM_DIR="/opt/nginx-proxy-manager" NETWORK_NAME="proxy" # ----------------------------- # Cores # ----------------------------- RESET="\033[0m" BOLD="\033[1m" DIM="\033[2m" GREEN="\033[38;5;42m" LIME="\033[38;5;118m" CYAN="\033[38;5;45m" BLUE="\033[38;5;39m" YELLOW="\033[38;5;220m" RED="\033[38;5;196m" WHITE="\033[38;5;255m" GRAY="\033[38;5;244m" clear_screen() { command -v clear >/dev/null 2>&1 && clear || true } line() { printf "${GRAY}%s${RESET}\n" "────────────────────────────────────────────────────────────────────────────" } banner() { clear_screen printf "${GREEN}${BOLD}" cat <<'EOF' █████╗ ██████╗ ████████╗██████╗ █████╗ ██████╗██╗ ██╗ ██╔══██╗██╔══██╗ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██║ ██╔╝ ███████║██║ ██║ ██║ ██████╔╝███████║██║ █████╔╝ ██╔══██║██║ ██║ ██║ ██╔══██╗██╔══██║██║ ██╔═██╗ ██║ ██║██████╔╝ ██║ ██║ ██║██║ ██║╚██████╗██║ ██╗ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ EOF printf "${RESET}" printf "\n" printf " ${WHITE}${BOLD}LINCE DIGITAL${RESET}\n" printf " ${GRAY}${DIM}BY LUCAS SANTANA${RESET}\n" printf "\n" printf "${GRAY}${DIM} Docker • Portainer • Nginx Proxy Manager • Infraestrutura pronta${RESET}\n" line printf "\n" } step() { local current="$1" local total="$2" local text="$3" printf "\n${CYAN}${BOLD}[ %02d/%02d ]${RESET} ${WHITE}${BOLD}%s${RESET}\n" "$current" "$total" "$text" } log() { printf "${BLUE} ➜${RESET} %s\n" "$*" } ok() { printf "${GREEN} ✔${RESET} %s\n" "$*" } warn() { printf "${YELLOW} ⚠${RESET} %s\n" "$*" } error() { printf "${RED} ✖ %s${RESET}\n" "$*" >&2 } fun_msg() { printf "${LIME} ☕ %s${RESET}\n" "$*" } info_box() { local title="$1" shift echo printf "${GRAY} ┌──────────────────────────────────────────────────────────────┐${RESET}\n" printf "${GRAY} │${RESET} ${WHITE}${BOLD}%-60s${RESET}${GRAY} │${RESET}\n" "$title" printf "${GRAY} ├──────────────────────────────────────────────────────────────┤${RESET}\n" for msg in "$@"; do printf "${GRAY} │${RESET} %-60s ${GRAY}│${RESET}\n" "$msg" done printf "${GRAY} └──────────────────────────────────────────────────────────────┘${RESET}\n" } trap 'echo; error "Algo deu errado na linha $LINENO. A instalação foi interrompida."; exit 1' ERR TOTAL_STEPS=8 banner printf "${WHITE}${BOLD}Preparando sua VPS para trabalhar sério.${RESET}\n" printf "${GRAY}Esse instalador vai deixar Docker, Portainer e Nginx Proxy Manager prontos.${RESET}\n" echo fun_msg "Preparando tudo por aqui... pode ir tomar um café." fun_msg "Pode ir validar como está o ROI que eu tô cuidando daqui." fun_msg "Preparando máquina matadora de ROI NEGATIVO." sleep 1 # ------------------------------------------------------------ # 1. Permissões e SO # ------------------------------------------------------------ step 1 "$TOTAL_STEPS" "Validando sistema" if [[ "${EUID}" -ne 0 ]]; then error "Execute como root: sudo bash $0" exit 1 fi if [[ ! -r /etc/os-release ]]; then error "Não foi possível identificar o sistema operacional." exit 1 fi . /etc/os-release DISTRO="${ID:-}" CODENAME="${VERSION_CODENAME:-}" ARCH="$(dpkg --print-architecture)" case "$DISTRO" in ubuntu|debian) ;; *) error "Distribuição não suportada: ${DISTRO}. Use Ubuntu ou Debian." exit 1 ;; esac ok "Sistema: ${PRETTY_NAME:-$DISTRO}" ok "Arquitetura: ${ARCH}" ok "Usuário: root" # ------------------------------------------------------------ # 2. Dependências # ------------------------------------------------------------ step 2 "$TOTAL_STEPS" "Preparando dependências do sistema" log "Atualizando índices do APT..." apt-get update -y >/dev/null log "Instalando ca-certificates, curl, gnupg e lsb-release..." DEBIAN_FRONTEND=noninteractive apt-get install -y \ ca-certificates \ curl \ gnupg \ lsb-release \ iproute2 >/dev/null ok "Dependências básicas prontas." fun_msg "Até aqui tudo certo. O café ainda deve estar quente." # ------------------------------------------------------------ # 3. Docker # ------------------------------------------------------------ step 3 "$TOTAL_STEPS" "Instalando Docker Engine" if command -v docker >/dev/null 2>&1; then ok "Docker já instalado: $(docker --version)" else log "Adicionando repositório oficial do Docker..." install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${DISTRO}/gpg" \ -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc if [[ -z "$CODENAME" ]]; then error "Não foi possível identificar VERSION_CODENAME." exit 1 fi cat > /etc/apt/sources.list.d/docker.sources </dev/null log "Instalando Docker Engine e plugins..." DEBIAN_FRONTEND=noninteractive apt-get install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-buildx-plugin \ docker-compose-plugin >/dev/null ok "Docker instalado com sucesso." fi systemctl enable --now docker >/dev/null 2>&1 if ! docker compose version >/dev/null 2>&1; then log "Docker Compose Plugin não encontrado. Instalando..." apt-get update -y >/dev/null DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin >/dev/null fi ok "$(docker --version)" ok "$(docker compose version)" # ------------------------------------------------------------ # 4. Portas # ------------------------------------------------------------ step 4 "$TOTAL_STEPS" "Verificando portas necessárias" port_in_use() { local port="$1" ss -lntH 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)$port$" } check_port() { local port="$1" local service="$2" if port_in_use "$port"; then error "A porta ${port} já está em uso. Necessária para ${service}." ss -lntp 2>/dev/null | grep -E ":${port}[[:space:]]" || true exit 1 fi ok "Porta ${port} disponível — ${service}" } if ! docker container inspect nginx-proxy-manager >/dev/null 2>&1; then check_port 80 "HTTP" check_port 81 "Painel do Nginx Proxy Manager" check_port 443 "HTTPS" else warn "Nginx Proxy Manager já existe. Pulando validação das portas 80/81/443." fi if ! docker container inspect portainer >/dev/null 2>&1; then check_port 9443 "Painel HTTPS do Portainer" else warn "Portainer já existe. Pulando validação da porta 9443." fi # ------------------------------------------------------------ # 5. Rede Docker # ------------------------------------------------------------ step 5 "$TOTAL_STEPS" "Criando rede compartilhada" if docker network inspect "$NETWORK_NAME" >/dev/null 2>&1; then ok "Rede '${NETWORK_NAME}' já existe." else docker network create "$NETWORK_NAME" >/dev/null ok "Rede Docker '${NETWORK_NAME}' criada." fi log "Essa rede será usada para conectar aplicações ao proxy reverso." # ------------------------------------------------------------ # 6. Portainer # ------------------------------------------------------------ step 6 "$TOTAL_STEPS" "Subindo Portainer CE" mkdir -p "$PORTAINER_DIR" cat > "$PORTAINER_DIR/docker-compose.yml" <<'EOF' services: portainer: image: portainer/portainer-ce:lts container_name: portainer restart: unless-stopped security_opt: - no-new-privileges:true ports: - "9443:9443" volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data networks: - proxy volumes: portainer_data: networks: proxy: external: true EOF log "Baixando imagem do Portainer..." ( cd "$PORTAINER_DIR" docker compose pull >/dev/null ) log "Iniciando Portainer..." ( cd "$PORTAINER_DIR" docker compose up -d ) ok "Portainer está rodando." fun_msg "Painel ficando bonito... igual relatório quando o ROAS sobe." # ------------------------------------------------------------ # 7. Nginx Proxy Manager # ------------------------------------------------------------ step 7 "$TOTAL_STEPS" "Subindo Nginx Proxy Manager" mkdir -p "$NPM_DIR/data" "$NPM_DIR/letsencrypt" cat > "$NPM_DIR/docker-compose.yml" <<'EOF' services: nginx-proxy-manager: image: jc21/nginx-proxy-manager:latest container_name: nginx-proxy-manager restart: unless-stopped security_opt: - no-new-privileges:true ports: - "80:80" - "81:81" - "443:443" environment: DISABLE_IPV6: "true" volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: - proxy networks: proxy: external: true EOF log "Baixando imagem do Nginx Proxy Manager..." ( cd "$NPM_DIR" docker compose pull >/dev/null ) log "Iniciando proxy reverso..." ( cd "$NPM_DIR" docker compose up -d ) ok "Nginx Proxy Manager está rodando." fun_msg "SSL, proxy e domínio no jeito. ROI negativo começando a ficar preocupado." # ------------------------------------------------------------ # 8. Diagnóstico final # ------------------------------------------------------------ step 8 "$TOTAL_STEPS" "Validando instalação" SERVER_IP="$(curl -4 -fsS --max-time 5 https://api.ipify.org 2>/dev/null || true)" PORTAINER_STATUS="$(docker inspect -f '{{.State.Status}}' portainer 2>/dev/null || echo "indisponível")" NPM_STATUS="$(docker inspect -f '{{.State.Status}}' nginx-proxy-manager 2>/dev/null || echo "indisponível")" if [[ "$PORTAINER_STATUS" == "running" ]]; then ok "Portainer: RUNNING" else warn "Portainer: ${PORTAINER_STATUS}" fi if [[ "$NPM_STATUS" == "running" ]]; then ok "Nginx Proxy Manager: RUNNING" else warn "Nginx Proxy Manager: ${NPM_STATUS}" fi echo line printf "${GREEN}${BOLD}" cat <<'EOF' ███████╗██╗ ██╗ ██████╗███████╗███████╗███████╗ ██████╗ ██╔════╝██║ ██║██╔════╝██╔════╝██╔════╝██╔════╝██╔═══██╗ ███████╗██║ ██║██║ █████╗ ███████╗███████╗██║ ██║ ╚════██║██║ ██║██║ ██╔══╝ ╚════██║╚════██║██║ ██║ ███████║╚██████╔╝╚██████╗███████╗███████║███████║╚██████╔╝ ╚══════╝ ╚═════╝ ╚═════╝╚══════╝╚══════╝╚══════╝ ╚═════╝ EOF printf "${RESET}" line echo printf "${WHITE}${BOLD}Sua VPS está pronta para receber aplicações.${RESET}\n" printf "${GRAY}AD TRACK • LINCE DIGITAL • BY LUCAS SANTANA${RESET}\n" if [[ -n "$SERVER_IP" ]]; then info_box "ACESSOS" \ "Portainer ............ https://${SERVER_IP}:9443" \ "Nginx Proxy Manager .. http://${SERVER_IP}:81" \ "HTTP ................. ${SERVER_IP}:80" \ "HTTPS ................ ${SERVER_IP}:443" else info_box "ACESSOS" \ "Portainer ............ https://IP-DA-VPS:9443" \ "Nginx Proxy Manager .. http://IP-DA-VPS:81" \ "HTTP ................. IP-DA-VPS:80" \ "HTTPS ................ IP-DA-VPS:443" fi info_box "ESTRUTURA CRIADA" \ "${PORTAINER_DIR}/docker-compose.yml" \ "${NPM_DIR}/docker-compose.yml" \ "${NPM_DIR}/data" \ "${NPM_DIR}/letsencrypt" \ "Rede Docker: ${NETWORK_NAME}" echo printf "${WHITE}${BOLD}Containers em execução:${RESET}\n" docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' echo warn "Lembre de liberar 80, 443, 81 e 9443 no firewall da VPS/provedor." warn "Depois da configuração, considere restringir 81 e 9443 por firewall ou VPN." echo fun_msg "Agora sim. Pode voltar do café." fun_msg "Infra pronta. Bora fazer essa máquina pagar a própria mensalidade." echo