#!/bin/bash set -uo pipefail set +H export PIP_NO_CACHE_DIR=1 export PIP_DISABLE_PIP_VERSION_CHECK=1 export GIT_TERMINAL_PROMPT=0 source /venv/main/bin/activate COMFYUI_DIR="${WORKSPACE:-/workspace}/ComfyUI" if [ -f "/venv/main/bin/python" ]; then PYTHON_BIN="/venv/main/bin/python" PIP_BIN="/venv/main/bin/pip" else PYTHON_BIN="python3" PIP_BIN="pip3" fi APT_PACKAGES=( "aria2" ) PIP_PACKAGES=( ) NODES=( "https://github.com/ltdrdata/ComfyUI-Impact-Pack.git" "https://github.com/ltdrdata/ComfyUI-Impact-Subpack.git" "https://github.com/cubiq/ComfyUI_IPAdapter_plus.git" "https://github.com/ka-puna/comfyui-yanc.git" "https://github.com/ltdrdata/ComfyUI-Inspire-Pack.git" "https://github.com/1038lab/ComfyUI-RMBG.git" "https://github.com/godmt/ComfyUI-List-Utils.git" "https://github.com/ltdrdata/was-node-suite-comfyui.git" ) BBOX_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/face_yolov9c.pt" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/hand_yolov9c.pt" ) CHECKPOINT_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/checkpoints/waiNSFWIllustrious_v150.safetensors" ) CLIP_VISION_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/clip_vision/CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors" ) DINO_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/grounding-dino/groundingdino_swinb_cogcoor.pth" ) FASHION_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/segformer_fashion/config.json" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/segformer_fashion/model.safetensors" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/segformer_fashion/preprocessor_config.json" ) IPADAPTER_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus-face_sdxl_vit-h.safetensors" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus_sdxl_vit-h.safetensors" ) LORAS_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/loras/Better_detailed_pussy_and_anus_v3.0.safetensors" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/loras/Hugkissingbreast_press_pov_Illustrious-000005.safetensors" ) RMBG_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/RMBG-2.0/BiRefNet_config.py" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/RMBG-2.0/birefnet.py" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/RMBG-2.0/config.json" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/RMBG/RMBG-2.0/model.safetensors" ) SAM2_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/sam2/sam2.1_hiera_large.safetensors" ) SAMS_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/sams/sam_vit_h_4b8939.pth" ) UPSCALE_MODELS=( "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/upscale_models/RealESRGAN_x4plus_anime_6B.pth" "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/upscale_models/4x-UltraSharpV2_Lite.pth" ) WORKFLOWS= WORKFLOWS=( ) log_info() { echo "--> $1"; } log_warn() { echo " ⚠️ $1"; } log_error() { echo " ❌ $1"; } log_success() { echo " ✅ $1"; } log_step() { echo ""; echo "=== [Step $1] $2 ==="; } package_installed() { $PYTHON_BIN -c "import $1" 2>/dev/null && return 0 || return 1 } filter_requirements() { local req_file="$1" local tmp_file="${req_file}.filtered" > "$tmp_file" while IFS= read -r line; do [[ -z "$line" || "$line" =~ ^# ]] && continue local pkg_name pkg_name=$(echo "$line" | sed 's/[<>=!].*//' | xargs) if [[ -z "$pkg_name" ]]; then continue; fi local module_name="${pkg_name//-/_}" if package_installed "$module_name"; then log_info "跳過已安裝套件: $pkg_name" else echo "$line" >> "$tmp_file" fi done < "$req_file" if [ -s "$tmp_file" ]; then mv "$tmp_file" "$req_file" return 0 else rm -f "$tmp_file" log_info "所有套件都已安裝" return 1 fi } provisioning_has_valid_hf_token() { [[ -n "${HF_TOKEN:-}" ]] || return 1 local response response=$(curl -o /dev/null -s -w "%{http_code}" -X GET "https://huggingface.co/api/whoami-v2" -H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: application/json") [ "$response" -eq 200 ] } provisioning_has_valid_civitai_token() { [[ -n "${CIVITAI_TOKEN:-}" ]] || return 1 local response response=$(curl -o /dev/null -s -w "%{http_code}" -X GET "https://civitai.com/api/v1/models?hidden=1&limit=1" -H "Authorization: Bearer $CIVITAI_TOKEN" -H "Content-Type: application/json") [ "$response" -eq 200 ] } install_node() { local repo_url="$1" local repo_name repo_name=$(basename "$repo_url" .git) local install_path="${COMFYUI_DIR}/custom_nodes/${repo_name}" if [ -d "$install_path" ]; then if [[ "${AUTO_UPDATE:-true}" != "false" ]]; then log_info "更新節點: $repo_name" (cd "$install_path" && git pull -q 2>&1 | grep -v "Already up to date" || true) else log_info "'$repo_name' 已存在,跳過" return fi else log_info "克隆節點: $repo_name" git clone --depth 1 --single-branch "$repo_url" "$install_path" -q 2>&1 || true fi if [ -f "$install_path/requirements.txt" ]; then log_info "處理 $repo_name 的依賴..." sed -i -e '/^torch/d' -e '/^sam2/d' "$install_path/requirements.txt" 2>/dev/null || true if filter_requirements "$install_path/requirements.txt"; then log_info "安裝 $repo_name 的新依賴..." $PIP_BIN install -q --no-cache-dir -r "$install_path/requirements.txt" 2>&1 | grep -v "Requirement already satisfied" || log_warn "部分依賴安裝失敗" fi fi if [ -f "$install_path/install.py" ]; then log_info "運行 $repo_name 的安裝腳本..." $PYTHON_BIN "$install_path/install.py" 2>&1 || log_warn "安裝腳本執行失敗" fi } download_file() { local dest_path="$1" local url="$2" local filename filename=$(basename "$dest_path") local tmp_path="${dest_path}.tmp" mkdir -p "$(dirname "$dest_path")" if [ -s "$dest_path" ]; then log_info "檔案 '$filename' 已存在且完整,跳過下載" return 0 fi log_info "下載: $filename" local max_retries=3 local attempt=0 local auth_header="" local success=1 if [[ "$url" =~ huggingface\.co ]] && provisioning_has_valid_hf_token; then auth_header="Authorization: Bearer $HF_TOKEN" log_info "使用 HuggingFace Token" elif [[ "$url" =~ civitai\.com ]] && provisioning_has_valid_civitai_token; then auth_header="Authorization: Bearer $CIVITAI_TOKEN" log_info "使用 CivitAI Token" fi while [ "$attempt" -lt "$max_retries" ]; do attempt=$((attempt + 1)) [ "$attempt" -gt 1 ] && sleep 10 if command -v aria2c >/dev/null 2>&1; then log_info "使用 aria2c (3 線程) 下載: $filename (嘗試 $attempt/$max_retries)" local aria_opts=(--console-log-level=error -c -x 3 -s 3 -k 1M --max-connection-per-server=3 --max-tries=3 --retry-wait=5 --timeout=180 --file-allocation=falloc --auto-file-renaming=false -d "$(dirname "$dest_path")" -o "${filename}.tmp") [[ -n "$auth_header" ]] && aria_opts+=(--header="$auth_header") aria2c "${aria_opts[@]}" "$url" if [ $? -eq 0 ]; then success=0; break; fi else log_info "使用 wget 下載: $filename (嘗試 $attempt/$max_retries)" local wget_opts=(-O "$tmp_path" -c --timeout=60 --tries=3 --content-disposition --show-progress) [[ -n "$auth_header" ]] && wget_opts+=(--header="$auth_header") wget "${wget_opts[@]}" "$url" if [ $? -eq 0 ]; then success=0; break; fi fi done if [ "$success" -eq 0 ] && [ -s "$tmp_path" ]; then mv "$tmp_path" "$dest_path" log_success "下載完成: $filename" return 0 else log_error "下載失敗: $filename" rm -f "$tmp_path" return 1 fi } download_to_directory() { local dest_dir="$1"; shift; local urls=("$@") if [ ${#urls[@]} -eq 0 ]; then return 0; fi mkdir -p "$dest_dir" log_info "下載 ${#urls[@]} 個文件到 $dest_dir" local MAX_PARALLEL=3 for url in "${urls[@]}"; do while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done local filename filename=$(basename "$url" | sed 's/?.*//') download_file "${dest_dir}/${filename}" "$url" & done wait } verify_and_retry_downloads() { local dest_dir="$1"; shift; local urls=("$@") if [ ${#urls[@]} -eq 0 ]; then return 0; fi log_info "檢查 $dest_dir 中的文件..." local missing_files=() for url in "${urls[@]}"; do local filename filename=$(basename "$url" | sed 's/?.*//') local dest_path="${dest_dir}/${filename}" if [ ! -s "$dest_path" ]; then log_warn "檔案缺失或不完整: $filename" missing_files+=("$url") fi done if [ ${#missing_files[@]} -gt 0 ]; then log_warn "發現 ${#missing_files[@]} 個缺失/不完整文件,重新下載..." download_to_directory "$dest_dir" "${missing_files[@]}" else log_success "所有文件已確認存在且完整" fi } provisioning_print_header() { echo ""; echo "##############################################" echo "# #" echo "# Provisioning Container #" echo "# #" echo "# This will take some time #" echo "# #" echo "##############################################"; echo "" } provisioning_print_end() { echo ""; echo "##############################################" echo "# #" echo "# Provisioning Complete! #" echo "# #" echo "# Total time: $((END_TIME - START_TIME)) seconds #" echo "# #" echo "##############################################"; echo "" } provisioning_step1_install_core_deps() { log_step "1" "安裝系統與 ComfyUI 核心依賴" if [ ${#APT_PACKAGES[@]} -gt 0 ]; then log_info "準備安裝 ${#APT_PACKAGES[@]} 個系統套件..." local packages_to_install=() for pkg in "${APT_PACKAGES[@]}"; do if ! dpkg -s "$pkg" >/dev/null 2>&1; then packages_to_install+=("$pkg") else log_success "$pkg 已安裝,跳過" fi done if [ ${#packages_to_install[@]} -gt 0 ]; then log_info "正在安裝: ${packages_to_install[*]}" apt-get update -qq && apt-get install -y -qq "${packages_to_install[@]}" && log_success "系統套件安裝完成" || log_warn "部分系統套件安裝失敗" fi fi local comfyui_req_file="${COMFYUI_DIR}/requirements.txt" if [ -f "$comfyui_req_file" ]; then log_info "處理 ComfyUI 核心依賴: $comfyui_req_file" if filter_requirements "$comfyui_req_file"; then log_info "安裝 ComfyUI 的新依賴..." $PIP_BIN install -q --no-cache-dir -r "$comfyui_req_file" 2>&1 | grep -v "Requirement already satisfied" || log_warn "ComfyUI 部分核心依賴安裝失敗" fi else log_warn "找不到 ComfyUI 的核心 requirements.txt 文件!" fi } provisioning_step2_nodes() { log_step "2" "安裝節點與額外套件" if [ ${#NODES[@]} -gt 0 ]; then cd "${COMFYUI_DIR}/custom_nodes" || exit 1 log_info "並行安裝 ${#NODES[@]} 個節點..." local MAX_PARALLEL=2 for node in "${NODES[@]}"; do while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done install_node "$node" & done wait log_success "節點安裝完成" cd "${COMFYUI_DIR}" || exit 1 fi } provisioning_step3_downloads() { log_step "3" "下載模型與工作流文件" download_to_directory "${COMFYUI_DIR}/models/checkpoints" "${CHECKPOINT_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/clip_vision" "${CLIP_VISION_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/grounding-dino" "${DINO_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/ipadapter" "${IPADAPTER_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/loras" "${LORAS_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/RMBG/RMBG-2.0" "${RMBG_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/RMBG/segformer_fashion" "${FASHION_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/sam2" "${SAM2_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/sams" "${SAMS_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/ultralytics/bbox" "${BBOX_MODELS[@]}" download_to_directory "${COMFYUI_DIR}/models/upscale_models" "${UPSCALE_MODELS[@]}" if [ ${#WORKFLOWS[@]} -gt 0 ]; then log_info "下載工作流文件..." download_to_directory "${COMFYUI_DIR}/user/default/workflows" "${WORKFLOWS[@]}" fi } if [ -f "/workspace/.provision_complete" ]; then log_success "環境已配置,跳過重複執行" log_info "如需重新配置,請刪除 /workspace/.provision_complete" exit 0 fi if [[ -f /.noprovisioning ]]; then log_warn "檢測到 /.noprovisioning 文件,跳過配置" exit 0 fi START_TIME=$(date +%s) provisioning_print_header cd "${COMFYUI_DIR}" || exit 1 provisioning_step1_install_core_deps provisioning_step2_nodes & NODE_PID=$! provisioning_step3_downloads wait $NODE_PID log_step "4" "驗證下載完整性" verify_and_retry_downloads "${COMFYUI_DIR}/models/checkpoints" "${CHECKPOINT_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/clip_vision" "${CLIP_VISION_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/grounding-dino" "${DINO_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/ipadapter" "${IPADAPTER_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/loras" "${LORAS_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/RMBG/RMBG-2.0" "${RMBG_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/RMBG/segformer_fashion" "${FASHION_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/sam2" "${SAM2_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/sams" "${SAMS_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/ultralytics/bbox" "${BBOX_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/models/upscale_models" "${UPSCALE_MODELS[@]}" verify_and_retry_downloads "${COMFYUI_DIR}/user/default/workflows" "${WORKFLOWS[@]}" log_step "5" "完成配置" touch "/workspace/.provision_complete" log_success "配置標記文件已創建" END_TIME=$(date +%s) provisioning_print_end log_success "所有配置步驟已完成!" exit 0