File size: 12,483 Bytes
374858d
 
32cab06
374858d
50bb958
 
 
 
 
 
32cab06
 
 
 
 
 
 
50bb958
e0df6dc
 
 
 
 
50bb958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8f7bcd
50bb958
 
374858d
 
 
 
 
32cab06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374858d
e0df6dc
 
 
 
 
 
 
 
 
 
dbb4774
e0df6dc
 
 
32cab06
374858d
 
32cab06
e0df6dc
 
 
 
 
 
7826453
50bb958
e0df6dc
50bb958
7826453
 
93ccf4a
e0df6dc
93ccf4a
 
 
32cab06
93ccf4a
dbb4774
93ccf4a
 
 
 
 
 
 
 
dbb4774
 
 
 
 
 
93ccf4a
dbb4774
 
93ccf4a
 
 
32cab06
 
 
 
 
 
 
 
 
e0df6dc
50bb958
 
 
7826453
e0df6dc
374858d
dbb4774
 
 
 
 
 
 
 
 
 
 
 
 
50bb958
 
374858d
50bb958
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/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"
)
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"
)
IPADAPTER_MODELS=(
    "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus-face_sdxl_vit-h.safetensors"
)
ESRGAN_MODELS=(
    "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/upscale_models/RealESRGAN_x4plus_anime_6B.pth"
)
WORKFLOWS=(
    "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/Single_Posture.json"
)

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/ipadapter" "${IPADAPTER_MODELS[@]}"
    download_to_directory "${COMFYUI_DIR}/models/upscale_models" "${ESRGAN_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/ipadapter" "${IPADAPTER_MODELS[@]}"
verify_and_retry_downloads "${COMFYUI_DIR}/models/upscale_models" "${ESRGAN_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