#!/usr/bin/env bash # ============================================================================= # Buskepainel — instalador oficial # Uso: curl -fsSL https://get.buskepainel.com.br | sudo bash # # O que este script faz: # 1. Verifica sistema operacional, arquitetura, recursos e portas # 2. Instala Docker Engine (repositório oficial da Docker) # 3. Inicializa Docker Swarm e cria a rede pública "buske-public" # 4. Gera segredos (senha do banco, APP_KEY, chave de criptografia) # 5. Sobe a stack: Traefik + Postgres + Redis + Buskepainel # 6. Mostra o endereço para criar a conta de administrador # # Variáveis opcionais (export antes de rodar): # BUSKE_VERSION=latest versão da imagem do painel # BUSKE_EMAIL=voce@x.com e-mail para o Let's Encrypt # BUSKE_DOMAIN=painel.x.com domínio do painel (senão usa IP:3000) # BUSKE_SKIP_CHECKS=1 pula checagem de recursos (não recomendado) # ============================================================================= set -Eeuo pipefail BUSKE_VERSION="${BUSKE_VERSION:-latest}" BUSKE_DIR="/data/buske" BUSKE_IMAGE="ghcr.io/buskepainel/panel:${BUSKE_VERSION}" # imagem publicada pelo CI do projeto BUSKE_NET="buske-public" MIN_RAM_MB=1800 MIN_DISK_GB=25 LOG_FILE="/var/log/buske-install.log" # ---------- saída ------------------------------------------------------------ c_ok=$'\e[32m'; c_warn=$'\e[33m'; c_err=$'\e[31m'; c_dim=$'\e[2m'; c_b=$'\e[1m'; c_0=$'\e[0m' passo() { printf '\n%s▸ %s%s\n' "$c_b" "$*" "$c_0"; } ok() { printf ' %s✔%s %s\n' "$c_ok" "$c_0" "$*"; } aviso() { printf ' %s!%s %s\n' "$c_warn" "$c_0" "$*"; } erro() { printf '\n %s✖ %s%s\n' "$c_err" "$*" "$c_0" >&2; exit 1; } trap 'erro "Falha na linha $LINENO. Veja o log em $LOG_FILE"' ERR exec > >(tee -a "$LOG_FILE") 2>&1 cat <<'BANNER' ____ _ _ _ | __ ) _ _ ___| | _____ _ __ __ _ _ __ (_) ___| | | _ \| | | / __| |/ / _ \ '_ \ / _` | '_ \| |/ _ \ | | |_) | |_| \__ \ < __/ |_) | (_| | | | | | __/ | |____/ \__,_|___/_|\_\___| .__/ \__,_|_| |_|_|\___|_| |_| painel de deploy brasileiro BANNER # ---------- 1. checagens ----------------------------------------------------- passo "Verificando o servidor" [[ $EUID -eq 0 ]] || erro "Rode como root: curl -fsSL https://get.buskepainel.com.br | sudo bash" [[ -r /etc/os-release ]] || erro "Não consegui identificar o sistema operacional." # shellcheck disable=SC1091 . /etc/os-release OS_ID="${ID:-}"; OS_VER="${VERSION_ID:-}"; OS_CODENAME="${VERSION_CODENAME:-}" case "${OS_ID}:${OS_VER}" in ubuntu:24.04) ok "Ubuntu 24.04 LTS (recomendado)";; ubuntu:26.04) ok "Ubuntu 26.04 LTS (suportado)";; ubuntu:22.04) aviso "Ubuntu 22.04 LTS funciona, mas prefira 24.04 em servidores novos";; debian:12|debian:13) ok "Debian ${OS_VER} (suportado)";; *) erro "Sistema ${PRETTY_NAME:-desconhecido} não suportado. Use Ubuntu 24.04 LTS, 26.04 LTS ou Debian 12/13.";; esac ARCH="$(dpkg --print-architecture)" case "$ARCH" in amd64|arm64) ok "Arquitetura $ARCH";; *) erro "Arquitetura $ARCH não suportada (use amd64 ou arm64).";; esac if [[ "${BUSKE_SKIP_CHECKS:-0}" != "1" ]]; then RAM_MB=$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo) DISK_GB=$(df -BG --output=avail / | tail -1 | tr -dc '0-9') CPUS=$(nproc) (( RAM_MB >= MIN_RAM_MB )) || erro "RAM insuficiente: ${RAM_MB} MB. Mínimo 2 GB (recomendado 4 GB)." (( DISK_GB >= MIN_DISK_GB )) || erro "Disco livre insuficiente: ${DISK_GB} GB. Mínimo ${MIN_DISK_GB} GB." ok "Recursos: ${CPUS} vCPU · ${RAM_MB} MB RAM · ${DISK_GB} GB livres" (( RAM_MB >= 3800 )) || aviso "Com menos de 4 GB, instale poucos apps ou crie swap." fi for porta in 80 443 3000; do if ss -ltnH "sport = :$porta" | grep -q .; then # porta ocupada por um Traefik/painel de instalação anterior é aceitável em reinstalação docker service ls 2>/dev/null | grep -q buske_ || erro "A porta $porta já está em uso. Libere-a (nginx/apache?) e rode de novo." fi done ok "Portas 80, 443 e 3000 disponíveis" # ---------- 2. pacotes base + swap ------------------------------------------- passo "Preparando o sistema" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq ca-certificates curl gnupg openssl jq ufw >/dev/null ok "Pacotes base instalados" if ! swapon --show | grep -q . && (( ${RAM_MB:-4096} < 8000 )); then fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab sysctl -q vm.swappiness=10; echo 'vm.swappiness=10' > /etc/sysctl.d/99-buske.conf ok "Swap de 2 GB criado" fi # ---------- 3. Docker -------------------------------------------------------- passo "Instalando Docker Engine" if command -v docker >/dev/null && docker info >/dev/null 2>&1; then ok "Docker já instalado: $(docker --version)" else for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do apt-get remove -y -qq "$pkg" >/dev/null 2>&1 || true done install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${OS_ID}/gpg" -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${OS_ID} ${OS_CODENAME} stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -qq apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null systemctl enable --now docker >/dev/null ok "$(docker --version)" fi # rotação de logs para não lotar o disco if [[ ! -f /etc/docker/daemon.json ]]; then cat > /etc/docker/daemon.json <<'JSON' { "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" }, "live-restore": false } JSON systemctl restart docker ok "Rotação de logs configurada" fi # ---------- 4. Swarm + rede -------------------------------------------------- passo "Configurando Docker Swarm" PUBLIC_IP="$(curl -fsS4 --max-time 5 https://api.ipify.org || hostname -I | awk '{print $1}')" if [[ "$(docker info --format '{{.Swarm.LocalNodeState}}')" != "active" ]]; then docker swarm init --advertise-addr "$PUBLIC_IP" >/dev/null ok "Swarm iniciado em $PUBLIC_IP" else ok "Swarm já ativo" fi docker network inspect "$BUSKE_NET" >/dev/null 2>&1 || \ docker network create --driver overlay --attachable "$BUSKE_NET" >/dev/null ok "Rede $BUSKE_NET pronta" # ---------- 5. segredos + arquivos ------------------------------------------- passo "Gerando configuração" mkdir -p "$BUSKE_DIR"/{traefik/dynamic,postgres,redis,panel,backups} touch "$BUSKE_DIR/traefik/acme.json" && chmod 600 "$BUSKE_DIR/traefik/acme.json" ENV_FILE="$BUSKE_DIR/.env" if [[ ! -f "$ENV_FILE" ]]; then cat > "$ENV_FILE" < "$BUSKE_DIR/stack.yml" </dev/null ufw allow 80/tcp >/dev/null ufw allow 443/tcp >/dev/null ufw allow 3000/tcp >/dev/null ufw allow 2377/tcp >/dev/null # gerenciamento do Swarm (para adicionar servidores) ufw allow 7946 >/dev/null ufw allow 4789/udp >/dev/null ufw --force enable >/dev/null ok "UFW ativo (SSH ${SSH_PORT:-22}, 80, 443, 3000, Swarm)" aviso "O Docker publica portas por fora do UFW. Só exponha portas de bancos se precisar." # ---------- 7. deploy -------------------------------------------------------- passo "Subindo o Buskepainel" docker stack deploy --detach=true -c "$BUSKE_DIR/stack.yml" buske >/dev/null printf ' aguardando o painel responder' for _ in $(seq 1 60); do if curl -fsS --max-time 2 "http://127.0.0.1:3000/health" >/dev/null 2>&1; then echo; ok "Painel no ar"; break; fi printf '.'; sleep 3 done echo URL="http://${PUBLIC_IP}:3000" [[ -n "${BUSKE_DOMAIN}" ]] && URL="https://${BUSKE_DOMAIN}" cat <