| #!/usr/bin/env bash |
| |
| |
|
|
| set -euo pipefail |
|
|
| |
| |
| |
| RED='\033[0;31m' |
| GREEN='\033[0;32m' |
| YELLOW='\033[1;33m' |
| BLUE='\033[0;34m' |
| NC='\033[0m' |
|
|
| log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } |
| log_success() { echo -e "${GREEN}[✓]${NC} $1"; } |
| log_warn() { echo -e "${YELLOW}[!]${NC} $1"; } |
| log_error() { echo -e "${RED}[✗]${NC} $1"; } |
|
|
| |
| |
| |
| COMFY_DIR="/root/comfy/ComfyUI" |
| MODELS_DIR="$COMFY_DIR/models" |
| COMFY_HOST="${COMFY_HOST:-0.0.0.0}" |
| COMFY_PORT="${COMFY_PORT:-8818}" |
| CIVITAI_TOKEN="${CIVITAI_TOKEN:-4fcb2834969399006a736ee402b061e5}" |
| HF_TOKEN="${HF_TOKEN:-}" |
|
|
| |
| export MAX_JOBS=32 |
| export NVCC_APPEND_FLAGS="--threads 8" |
| export UV_SYSTEM_PYTHON=1 |
| export PYTORCH_CUDA_ALLOC_CONF="expandable_segments:True" |
| export HF_HUB_ENABLE_HF_TRANSFER=1 |
| export HF_TRANSFER_CONCURRENCY=16 |
| export EXT_PARALLEL=4 |
|
|
| |
| |
| |
| |
| readonly DOWNLOAD_FILES=( |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| "hf://xinsir/controlnet-union-sdxl-1.0/diffusion_pytorch_model_promax.safetensors|controlnet|controlnet-union.safetensors" |
| "https://huggingface.co/ABDALLALSWAITI/Upscalers/resolve/main/anime/2x-AnimeSharpV2_MoSR_Soft.pth|upscale_models|2x-AnimeSharpV2_MoSR_Soft.pth" |
| |
| "https://civitai.com/api/download/models/2122278?type=Model&format=SafeTensor&size=pruned&fp=fp16|checkpoints|raehoshiIllustXL_v60.safetensors" |
| ) |
|
|
| |
| readonly CUSTOM_NODES=( |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| "https://github.com/CoreyCorza/ComfyUI-CRZnodes" |
| |
| |
| |
| ) |
|
|
| |
| |
| |
| command_exists() { |
| command -v "$1" >/dev/null 2>&1 |
| } |
|
|
| |
| add_civitai_token() { |
| local url="$1" |
| if [[ "$url" == *"civitai.com/api/download"* ]] && [[ "$url" != *"token="* ]]; then |
| echo "${url}&token=${CIVITAI_TOKEN}" |
| else |
| echo "$url" |
| fi |
| } |
|
|
| |
| download_hf() { |
| local repo="$1" |
| local file_path="$2" |
| local target_dir="$3" |
| local filename="$4" |
| |
| |
| if [ -z "$filename" ]; then |
| filename=$(basename "$file_path") |
| fi |
| |
| local target_file="$target_dir/$filename" |
| |
| |
| if [ -f "$target_file" ] && [ $(stat -c%s "$target_file" 2>/dev/null || echo 0) -gt 1000000 ]; then |
| log_success "Arquivo já existe: $filename" |
| return 0 |
| fi |
| |
| log_info "Baixando de HF: $filename" |
| |
| |
| if HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download "$repo" "$file_path" \ |
| --local-dir "$target_dir" \ |
| --local-dir-use-symlinks False \ |
| --resume-download 2>/dev/null; then |
| |
| |
| if [ -f "$target_file" ]; then |
| log_success "Download concluído: $filename" |
| return 0 |
| fi |
| |
| |
| local downloaded_file=$(find "$target_dir" -name "$filename" -type f 2>/dev/null | head -1) |
| if [ -n "$downloaded_file" ] && [ "$downloaded_file" != "$target_file" ]; then |
| mv "$downloaded_file" "$target_file" |
| log_success "Download concluído e movido: $filename" |
| return 0 |
| fi |
| fi |
| |
| log_error "Falha ao baixar: $filename" |
| return 1 |
| } |
|
|
| |
| download_file() { |
| local url="$1" |
| local target_dir="$2" |
| local filename="$3" |
| |
| |
| url=$(add_civitai_token "$url") |
| |
| |
| if [ -n "$filename" ] && [ -f "$target_dir/$filename" ] && [ $(stat -c%s "$target_dir/$filename" 2>/dev/null || echo 0) -gt 1000000 ]; then |
| log_success "Arquivo já existe: $filename" |
| return 0 |
| fi |
| |
| log_info "Baixando: ${filename:-$(basename "$url" | cut -d'?' -f1)}" |
| |
| |
| if command_exists aria2c; then |
| local aria_opts="-c -s 16 -x 16 -k 1M --console-log-level=warn --summary-interval=10" |
| if [ -n "$filename" ]; then |
| aria2c $aria_opts --dir="$target_dir" --out="$filename" "$url" && return 0 |
| else |
| aria2c $aria_opts --dir="$target_dir" "$url" && return 0 |
| fi |
| fi |
| |
| |
| if command_exists wget; then |
| if [ -n "$filename" ]; then |
| wget -q --show-progress -c -O "$target_dir/$filename" "$url" && return 0 |
| else |
| wget -q --show-progress -c --content-disposition -P "$target_dir" "$url" && return 0 |
| fi |
| fi |
| |
| |
| if command_exists curl; then |
| if [ -n "$filename" ]; then |
| curl -L - |
| else |
| local headers=$(curl -sI -L "$url") |
| local detected_name=$(echo "$headers" | grep -i "content-disposition" | sed -n 's/.*filename="\([^"]*\)".*/\1/p' | tr -d '\r') |
| if [ -z "$detected_name" ]; then |
| detected_name="downloaded_$(date +%s).safetensors" |
| fi |
| curl -L - |
| fi |
| fi |
| |
| log_error "Falha ao baixar: $url" |
| return 1 |
| } |
|
|
| |
| process_downloads() { |
| local failed_count=0 |
| |
| for entry in "${DOWNLOAD_FILES[@]}"; do |
| IFS='|' read -r url type filename <<< "$entry" |
| |
| |
| url=$(echo "$url" | xargs) |
| type=$(echo "$type" | xargs) |
| filename=$(echo "$filename" | xargs) |
| |
| |
| local target_dir="$MODELS_DIR" |
| case "$type" in |
| checkpoints) target_dir="$MODELS_DIR/checkpoints" ;; |
| loras) target_dir="$MODELS_DIR/loras" ;; |
| vae) target_dir="$MODELS_DIR/vae" ;; |
| text_encoders) target_dir="$MODELS_DIR/text_encoders" ;; |
| clip_vision) target_dir="$MODELS_DIR/clip_vision" ;; |
| controlnet) target_dir="$MODELS_DIR/controlnet" ;; |
| upscale_models) target_dir="$MODELS_DIR/upscale_models" ;; |
| *) target_dir="$MODELS_DIR/$type" ;; |
| esac |
| |
| |
| mkdir -p "$target_dir" |
| |
| |
| if [[ "$url" == hf://* ]]; then |
| |
| url="${url#hf://}" |
| repo=$(echo "$url" | cut -d'/' -f1-2) |
| file_path=$(echo "$url" | cut -d'/' -f3-) |
| |
| download_hf "$repo" "$file_path" "$target_dir" "$filename" || ((failed_count++)) |
| else |
| |
| download_file "$url" "$target_dir" "$filename" || ((failed_count++)) |
| fi |
| done |
| |
| |
| if [ -d "$MODELS_DIR/diffusion_models/split_files" ]; then |
| log_info "Movendo arquivos de split_files para diretórios corretos..." |
| find "$MODELS_DIR" -path "*/split_files/*" -name "*.safetensors" | while read -r file; do |
| local base_dir=$(dirname "$(dirname "$file")") |
| local target_name=$(basename "$file") |
| if [ ! -f "$base_dir/$target_name" ]; then |
| mv "$file" "$base_dir/" 2>/dev/null && log_success "Movido: $target_name" |
| fi |
| done |
| |
| find "$MODELS_DIR" -type d -name "split_files" -empty -delete 2>/dev/null |
| fi |
| |
| return $failed_count |
| } |
|
|
| |
| clone_or_update() { |
| local url="$1" |
| local dest="$2" |
| local node_name=$(basename "$dest") |
| |
| |
| if [ -d "$dest/.git" ]; then |
| log_info "Atualizando $node_name..." |
| timeout 60 git -C "$dest" pull --ff-only 2>/dev/null || { |
| log_warn "Timeout ou erro ao atualizar $node_name" |
| return 0 |
| } |
| else |
| log_info "Clonando $node_name..." |
| timeout 60 git clone --recursive --depth 1 "$url" "$dest" 2>/dev/null || { |
| log_warn "Falha ao clonar $node_name" |
| return 0 |
| } |
| fi |
| |
| |
| if [ -f "$dest/requirements.txt" ]; then |
| timeout 120 python3 -m pip install --no-warn-script-location -q -r "$dest/requirements.txt" 2>/dev/null || { |
| log_warn "Falha ao instalar requirements para $node_name" |
| } |
| fi |
| |
| return 0 |
| } |
|
|
| |
| |
| |
| echo "" |
| log_info "=========================================" |
| log_info " ComfyUI + Wan 2.2 Setup (Fixed)" |
| log_info "=========================================" |
| echo "" |
|
|
| |
| log_info "[1/8] Verificando e instalando dependências..." |
|
|
| |
| if ! command_exists aria2c; then |
| log_info "Instalando aria2c do sistema..." |
| apt-get update -qq && apt-get install -y -qq aria2 2>/dev/null |
| fi |
|
|
| for cmd in python3 git wget curl; do |
| if ! command_exists "$cmd"; then |
| log_error "Dependência faltando: $cmd" |
| exit 1 |
| fi |
| done |
| log_success "Dependências OK" |
|
|
| |
| log_info "[2/8] Preparando ferramentas Python..." |
| python3 -m pip install --upgrade pip wheel setuptools -q |
|
|
| |
| python3 -m pip install --upgrade "huggingface_hub[cli]>=0.26.0" hf_transfer comfy-cli -q |
|
|
| |
| HF_TOKEN="${HF_TOKEN:-}" |
| if [ -n "$HF_TOKEN" ]; then |
| if huggingface-cli login --token "$HF_TOKEN" 2>/dev/null; then |
| log_success "Login HF configurado" |
| else |
| log_warn "Falha ao configurar login HF, continuando sem autenticação" |
| fi |
| fi |
|
|
| |
| if command_exists huggingface-cli; then |
| log_success "huggingface-cli disponível" |
| else |
| log_warn "huggingface-cli não encontrado" |
| fi |
|
|
| log_success "Ferramentas instaladas" |
|
|
| |
| log_info "[3/8] Instalando PyTorch com CUDA 12.8..." |
| python3 -m pip install --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128 |
| log_success "PyTorch com CUDA 12.8 instalado" |
|
|
| |
| log_info "[4/8] Instalando ComfyUI..." |
| if [ -f "$COMFY_DIR/main.py" ]; then |
| log_warn "ComfyUI já existe, pulando instalação base" |
| else |
| comfy --skip-prompt --skip-manager install --fast-deps --nvidia |
| |
| |
| if [ ! -f "$COMFY_DIR/main.py" ]; then |
| log_error "ComfyUI não foi instalado corretamente!" |
| exit 1 |
| fi |
| fi |
| log_success "ComfyUI instalado em: $COMFY_DIR" |
|
|
| |
| log_info "[5/8] Instalando SageAttention..." |
| if command_exists nvcc || python3 -c "import torch; print(torch.cuda.is_available())" 2>/dev/null | grep -q "True"; then |
| log_info "Instalando SageAttention pré-compilada..." |
| python3 -m pip install "https://huggingface.co/adbrasi/comfywheel/resolve/main/sageattention-2.2.0-cp311-cp311-linux_x86_64.whl" -q |
| |
| |
| if python3 -c "import sageattention" 2>/dev/null; then |
| log_success "SageAttention instalado com sucesso" |
| else |
| log_warn "SageAttention instalado mas não pôde ser importado" |
| fi |
| else |
| log_warn "CUDA não detectado, pulando SageAttention" |
| fi |
|
|
| |
| log_info "[6/8] Baixando modelos..." |
| mkdir -p "$MODELS_DIR"/{diffusion_models,loras,vae,text_encoders,clip_vision,controlnet,upscale_models} |
|
|
| |
| if command_exists aria2c; then |
| log_success "Usando aria2c para downloads (rápido)" |
| else |
| log_warn "aria2c não encontrado, usando wget/curl (mais lento)" |
| fi |
|
|
| |
| if process_downloads; then |
| log_success "Todos os modelos baixados com sucesso" |
| else |
| log_warn "Alguns downloads falharam, mas continuando..." |
| fi |
|
|
| |
| log_info "[7/8] Instalando custom nodes..." |
| CN_DIR="$COMFY_DIR/custom_nodes" |
| mkdir -p "$CN_DIR" |
|
|
| |
| for repo_url in "${CUSTOM_NODES[@]}"; do |
| node_name=$(basename "$repo_url") |
| clone_or_update "$repo_url" "$CN_DIR/$node_name" |
| done |
|
|
| log_success "Custom nodes instalados" |
|
|
| |
| echo "" |
| log_info "[8/8] Verificando instalação..." |
|
|
| |
| check_critical_files() { |
| local files=( |
| "$COMFY_DIR/main.py" |
| "$MODELS_DIR/diffusion_models/wan2.2_fun_control_low_noise_14B_fp8_scaled.safetensors" |
| "$MODELS_DIR/diffusion_models/wan2.2_fun_control_high_noise_14B_fp8_scaled.safetensors" |
| "$MODELS_DIR/vae/wan_2.1_vae.safetensors" |
| "$MODELS_DIR/text_encoders/umt5_xxl_fp16.safetensors" |
| ) |
| |
| local missing=0 |
| for file in "${files[@]}"; do |
| if [ -f "$file" ]; then |
| log_success "✓ $(basename "$file")" |
| else |
| log_error "✗ $(basename "$file") não encontrado" |
| ((missing++)) |
| fi |
| done |
| |
| return $missing |
| } |
|
|
| if check_critical_files; then |
| log_success "Instalação concluída com sucesso!" |
| echo "" |
| log_info "=========================================" |
| log_info "Iniciando ComfyUI..." |
| log_info "URL: http://localhost:$COMFY_PORT" |
| log_info "=========================================" |
| |
| |
| SAGE_FLAG="" |
| if python3 -c "import sageattention" 2>/dev/null; then |
| SAGE_FLAG="--use-sage-attention" |
| log_info "SageAttention habilitado" |
| fi |
| |
| cd "$COMFY_DIR" |
| exec comfy launch -- $SAGE_FLAG --listen "$COMFY_HOST" --port "$COMFY_PORT" |
| else |
| log_error "Arquivos críticos faltando. Verifique os erros acima." |
| |
| |
| log_info "Procurando arquivos mal posicionados..." |
| find "$MODELS_DIR" -name "*.safetensors" -type f | head -20 |
| |
| exit 1 |
| fi |