JCscrew commited on
Commit
dbb4774
·
verified ·
1 Parent(s): 1ccab35

Update script.sh

Browse files
Files changed (1) hide show
  1. script.sh +100 -317
script.sh CHANGED
@@ -45,157 +45,41 @@ WORKFLOWS=(
45
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/Single_Posture.json"
46
  )
47
 
48
- PREINSTALLED_PACKAGES=(
49
- "torch"
50
- "torchvision"
51
- "torchaudio"
52
- "numpy"
53
- "pillow"
54
- "pyyaml"
55
- "tqdm"
56
- "requests"
57
- "opencv-python"
58
- "scipy"
59
- )
60
-
61
  log_info() { echo "--> $1"; }
62
  log_warn() { echo " ⚠️ $1"; }
63
  log_error() { echo " ❌ $1"; }
64
  log_success() { echo " ✅ $1"; }
65
  log_step() { echo ""; echo "=== [Step $1] $2 ==="; }
66
-
67
- package_installed() {
68
- $PYTHON_BIN -c "import $1" 2>/dev/null && return 0 || return 1
69
- }
70
-
71
- filter_requirements() {
72
- local req_file="$1"
73
- local tmp_file="${req_file}.filtered"
74
-
75
- > "$tmp_file"
76
-
77
- while IFS= read -r line; do
78
- [[ -z "$line" || "$line" =~ ^# ]] && continue
79
-
80
- local pkg_name
81
- pkg_name=$(echo "$line" | sed 's/[<>=!].*//' | xargs)
82
-
83
- if [[ -z "$pkg_name" ]]; then
84
- continue
85
- fi
86
-
87
- local module_name="${pkg_name//-/_}"
88
-
89
- if package_installed "$module_name"; then
90
- log_info "跳過已安裝套件: $pkg_name"
91
- else
92
- echo "$line" >> "$tmp_file"
93
- fi
94
- done < "$req_file"
95
-
96
- if [ -s "$tmp_file" ]; then
97
- mv "$tmp_file" "$req_file"
98
- return 0
99
- else
100
- rm -f "$tmp_file"
101
- log_info "所有套件都已安裝"
102
- return 1
103
- fi
104
- }
105
-
106
- if [ -f "/workspace/.provision_complete" ]; then
107
- log_success "環境已配置,跳過重複執行"
108
- log_info "如需重新配置,請刪除 /workspace/.provision_complete"
109
- exit 0
110
- fi
111
-
112
- if [[ -f /.noprovisioning ]]; then
113
- log_warn "檢測到 /.noprovisioning 文件,跳過配置"
114
- exit 0
115
- fi
116
-
117
- if [ -f "/venv/main/bin/python" ]; then
118
- PYTHON_BIN="/venv/main/bin/python"
119
- PIP_BIN="/venv/main/bin/pip"
120
- else
121
- PYTHON_BIN="python3"
122
- PIP_BIN="pip3"
123
- fi
124
-
125
- provisioning_has_valid_hf_token() {
126
- [[ -n "${HF_TOKEN:-}" ]] || return 1
127
- local response
128
- response=$(curl -o /dev/null -s -w "%{http_code}" -X GET \
129
- "https://huggingface.co/api/whoami-v2" \
130
- -H "Authorization: Bearer $HF_TOKEN" \
131
- -H "Content-Type: application/json")
132
- [ "$response" -eq 200 ]
133
- }
134
-
135
- provisioning_has_valid_civitai_token() {
136
- [[ -n "${CIVITAI_TOKEN:-}" ]] || return 1
137
- local response
138
- response=$(curl -o /dev/null -s -w "%{http_code}" -X GET \
139
- "https://civitai.com/api/v1/models?hidden=1&limit=1" \
140
- -H "Authorization: Bearer $CIVITAI_TOKEN" \
141
- -H "Content-Type: application/json")
142
- [ "$response" -eq 200 ]
143
- }
144
-
145
- install_node() {
146
- local repo_url="$1"
147
- local branch_name="${2:-}"
148
- local repo_name
149
- repo_name=$(basename "$repo_url" .git)
150
- local install_path="${COMFYUI_DIR}/custom_nodes/${repo_name}"
151
-
152
- if [ -d "$install_path" ]; then
153
- if [[ "${AUTO_UPDATE:-true}" != "false" ]]; then
154
- log_info "更新節點: $repo_name"
155
- (cd "$install_path" && git pull -q 2>&1 | grep -v "Already up to date" || true)
156
- else
157
- log_info "'$repo_name' 已存在,跳過"
158
- return
159
- fi
160
- else
161
- log_info "克隆節點: $repo_name"
162
- if [ -n "$branch_name" ]; then
163
- git clone --depth 1 --single-branch --branch "$branch_name" "$repo_url" "$install_path" -q 2>&1 || true
164
- else
165
- git clone --depth 1 --single-branch "$repo_url" "$install_path" -q 2>&1 || true
166
- fi
167
- fi
168
-
169
- if [ -f "$install_path/requirements.txt" ]; then
170
- log_info "處理 $repo_name 的依賴..."
171
-
172
- sed -i -e '/^torch/d' -e '/^sam2/d' "$install_path/requirements.txt" 2>/dev/null || true
173
-
174
- if filter_requirements "$install_path/requirements.txt"; then
175
- log_info "安裝 $repo_name 的新依賴..."
176
- $PIP_BIN install -q --no-cache-dir -r "$install_path/requirements.txt" 2>&1 | grep -v "Requirement already satisfied" || log_warn "部分依賴安裝失敗"
177
- fi
178
- fi
179
-
180
- if [ -f "$install_path/install.py" ]; then
181
- log_info "運行 $repo_name 的安裝腳本..."
182
- $PYTHON_BIN "$install_path/install.py" 2>&1 || log_warn "安裝腳本執行失敗"
183
- fi
184
- }
185
 
186
  download_file() {
187
  local dest_path="$1"
188
  local url="$2"
189
  local filename
190
  filename=$(basename "$dest_path")
191
-
 
192
  mkdir -p "$(dirname "$dest_path")"
193
 
194
- log_info "開始下載: $filename"
 
 
 
 
 
195
 
196
  local max_retries=3
197
  local attempt=0
198
  local auth_header=""
 
199
 
200
  if [[ "$url" =~ huggingface\.co ]] && provisioning_has_valid_hf_token; then
201
  auth_header="Authorization: Bearer $HF_TOKEN"
@@ -212,258 +96,157 @@ download_file() {
212
  if command -v aria2c >/dev/null 2>&1; then
213
  log_info "使用 aria2c (3 線程) 下載: $filename (嘗試 $attempt/$max_retries)"
214
  local aria_opts=(
215
- --console-log-level=error
216
- -c -x 3 -s 3 -k 1M
217
- --max-connection-per-server=3
218
- --max-tries=3
219
- --retry-wait=5
220
- --timeout=180
221
- --file-allocation=falloc
222
- --auto-file-renaming=false
223
- -d "$(dirname "$dest_path")"
224
- -o "$filename"
225
  )
226
-
227
  [[ -n "$auth_header" ]] && aria_opts+=(--header="$auth_header")
228
 
229
- aria2c "${aria_opts[@]}" "$url" 2>&1 && break
 
 
 
 
230
  else
231
  log_info "使用 wget 下載: $filename (嘗試 $attempt/$max_retries)"
232
  local wget_opts=(
233
- -qnc -O "$dest_path"
234
- --timeout=60
235
- --tries=3
236
- --content-disposition
237
- --show-progress
238
  )
239
  [[ -n "$auth_header" ]] && wget_opts+=(--header="$auth_header")
240
 
241
- wget "${wget_opts[@]}" "$url" 2>&1 && break
 
 
 
 
242
  fi
243
  done
244
 
245
- if [ -f "$dest_path" ]; then
246
- # Verification will be handled by sync_and_verify, just log success here.
 
247
  return 0
248
  else
249
  log_error "下載失敗: $filename"
 
250
  return 1
251
  fi
252
  }
253
 
254
- sync_and_verify() {
255
- local dest_dir="$1"
256
- shift
257
- local urls=("$@")
258
-
259
  if [ ${#urls[@]} -eq 0 ]; then return 0; fi
260
-
261
- local urls_to_download=()
262
  mkdir -p "$dest_dir"
 
 
 
 
 
 
 
 
 
263
 
264
- log_info "開始驗證 $dest_dir 中的文件..."
265
-
 
 
 
 
266
  for url in "${urls[@]}"; do
267
- local filename
268
- filename=$(basename "$url" | sed 's/?.*//')
269
  local dest_path="${dest_dir}/${filename}"
270
 
271
- local expected_sha256=""
272
- if [[ "$url" =~ huggingface\.co ]]; then
273
- expected_sha256=$(curl -sI "$url?lfs=true" | grep -i 'x-linked-sha256' | awk -F': ' '{print $2}' | tr -d '\r')
274
- fi
275
-
276
- if [ -z "$expected_sha256" ]; then
277
- log_warn "無法獲取 '$filename' 的校驗和,將僅檢查文件是否存在。"
278
- if [ ! -f "$dest_path" ]; then
279
- log_info "文件不存在,加入下載列表: $filename"
280
- urls_to_download+=("$url")
281
- else
282
- log_success "文件已存在 (跳過校驗): $filename"
283
- fi
284
- continue
285
- fi
286
-
287
- if [ -f "$dest_path" ]; then
288
- local local_sha256
289
- local_sha256=$(sha256sum "$dest_path" | awk '{print $1}')
290
- if [ "$local_sha256" == "$expected_sha256" ]; then
291
- log_success "文件校驗通過: $filename"
292
- else
293
- log_warn "文件校驗和不匹配: $filename"
294
- log_warn " --> 預期: $expected_sha256"
295
- log_warn " --> 得到: $local_sha256"
296
- rm -f "$dest_path"
297
- log_info "已刪除損壞文件,將重新下載。"
298
- urls_to_download+=("$url")
299
- fi
300
- else
301
- log_info "文件不存在,加入下載列表: $filename"
302
- urls_to_download+=("$url")
303
  fi
304
  done
305
 
306
- if [ ${#urls_to_download[@]} -gt 0 ]; then
307
- log_info "準備下載 ${#urls_to_download[@]} 個新文件或無效文件到 $dest_dir"
308
- local MAX_PARALLEL=3
309
- for url in "${urls_to_download[@]}"; do
310
- while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do
311
- sleep 1
312
- done
313
- local filename
314
- filename=$(basename "$url" | sed 's/?.*//')
315
- download_file "${dest_dir}/${filename}" "$url" &
316
- done
317
- wait
318
-
319
- log_info "重新驗證下載的文件..."
320
- for url in "${urls_to_download[@]}"; do
321
- local filename
322
- filename=$(basename "$url" | sed 's/?.*//')
323
- local dest_path="${dest_dir}/${filename}"
324
-
325
- if [ ! -f "$dest_path" ]; then
326
- log_error "重試下載後文件依然缺失: $filename"
327
- continue
328
- fi
329
-
330
- local expected_sha256=""
331
- if [[ "$url" =~ huggingface\.co ]]; then
332
- expected_sha256=$(curl -sI "$url?lfs=true" | grep -i 'x-linked-sha256' | awk -F': ' '{print $2}' | tr -d '\r')
333
- fi
334
-
335
- if [ -n "$expected_sha256" ]; then
336
- local local_sha256
337
- local_sha256=$(sha256sum "$dest_path" | awk '{print $1}')
338
- if [ "$local_sha256" == "$expected_sha256" ]; then
339
- log_success "重新下載的文件校驗通過: $filename"
340
- else
341
- log_error "重新下載的文件校驗失敗: $filename"
342
- fi
343
- fi
344
- done
345
-
346
  else
347
- log_success "$dest_dir 中的所有文件均已驗證且為最新。"
348
  fi
349
  }
350
 
351
-
352
- provisioning_print_header() {
353
- echo ""
354
- echo "##############################################"
355
- echo "# #"
356
- echo "# Provisioning Container #"
357
- echo "# #"
358
- echo "# This will take some time #"
359
- echo "# #"
360
- echo "##############################################"
361
- echo ""
362
- }
363
-
364
- provisioning_print_end() {
365
- echo ""
366
- echo "##############################################"
367
- echo "# #"
368
- echo "# Provisioning Complete! #"
369
- echo "# #"
370
- echo "# Total time: $((END_TIME - START_TIME)) seconds"
371
- echo "# #"
372
- echo "##############################################"
373
- echo ""
374
- }
375
 
376
  provisioning_step2_nodes() {
377
  log_step "2" "安裝節點與套件"
378
-
379
  if [ ${#NODES[@]} -gt 0 ]; then
380
  cd "${COMFYUI_DIR}/custom_nodes" || exit 1
381
  log_info "並行安裝 ${#NODES[@]} 個節點..."
382
-
383
  MAX_PARALLEL=2
384
  for node in "${NODES[@]}"; do
385
- while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do
386
- sleep 1
387
- done
388
  install_node "$node" &
389
  done
390
  wait
391
-
392
  log_success "節點安裝完成"
393
  cd "${COMFYUI_DIR}" || exit 1
394
  fi
395
-
396
  if [ ${#PIP_PACKAGES[@]:-} -gt 0 ]; then
397
  log_info "檢查並安裝額外的 Python 套件..."
398
-
399
  local packages_to_install=()
400
  for pkg in "${PIP_PACKAGES[@]}"; do
401
- local pkg_name="${pkg/[<>=!].*/}"
402
- local module_name="${pkg_name//-/_}"
403
-
404
- if package_installed "$module_name"; then
405
- log_info "跳過已安裝套件: $pkg_name"
406
- else
407
- packages_to_install+=("$pkg")
408
- fi
409
  done
410
-
411
- if [ ${#packages_to_install[@]} -gt 0 ]; then
412
- $PIP_BIN install -q --no-cache-dir "${packages_to_install[@]}" 2>&1 | grep -v "Requirement already satisfied" || true
413
- fi
414
  fi
415
  }
416
 
417
- provisioning_step3_models_and_workflows() {
418
- log_step "3" "下載並驗證模型與工作流"
419
-
420
- sync_and_verify "${COMFYUI_DIR}/models/checkpoints" "${CHECKPOINT_MODELS[@]}"
421
- sync_and_verify "${COMFYUI_DIR}/models/clip_vision" "${CLIP_VISION_MODELS[@]}"
422
- sync_and_verify "${COMFYUI_DIR}/models/ipadapter" "${IPADAPTER_MODELS[@]}"
423
- sync_and_verify "${COMFYUI_DIR}/models/sams" "${SAM_MODELS[@]}"
424
- sync_and_verify "${COMFYUI_DIR}/models/ultralytics/bbox" "${ULTRALYTICS_BBOX_MODELS[@]}"
425
- sync_and_verify "${COMFYUI_DIR}/models/upscale_models" "${ESRGAN_MODELS[@]}"
426
-
427
  if [ ${#WORKFLOWS[@]} -gt 0 ]; then
428
- sync_and_verify "${COMFYUI_DIR}/user/default/workflows" "${WORKFLOWS[@]}"
 
429
  fi
430
  }
431
 
432
  START_TIME=$(date +%s)
433
  log_info "開始時間: $(date)"
434
  provisioning_print_header
435
-
436
  cd "${COMFYUI_DIR}" || exit 1
437
 
438
- log_step "1" "安裝系統套件"
439
-
440
- COMMANDS_TO_CHECK=("aria2c" "curl" "sha256sum")
441
- MISSING_COMMANDS=()
442
- for cmd in "${COMMANDS_TO_CHECK[@]}"; do
443
- if ! command -v $cmd >/dev/null 2>&1; then
444
- MISSING_COMMANDS+=($cmd)
445
- fi
446
- done
447
-
448
- if [ ${#MISSING_COMMANDS[@]} -gt 0 ]; then
449
- log_info "缺少必要套件: ${MISSING_COMMANDS[*]}. 開始安裝..."
450
  if command -v sudo &>/dev/null && sudo -n true 2>/dev/null; then
451
- sudo apt-get update -qq
452
- sudo apt-get install -y -qq aria2 curl coreutils
453
- log_success "套件安裝完成"
454
  else
455
- log_error "無 sudo 權限,無法安裝缺失套件。請手動安裝: ${MISSING_COMMANDS[*]}"
456
- exit 1
457
  fi
458
  else
459
- log_success "所有必要套件 (aria2, curl, sha256sum) 均已安裝。"
460
  fi
461
 
462
- provisioning_step2_nodes
463
- provisioning_step3_models_and_workflows
464
-
465
- log_step "4" "完成配置"
466
-
 
 
 
 
 
 
 
 
 
 
467
  touch "/workspace/.provision_complete"
468
  log_success "配置標記文件已創建"
469
 
 
45
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/Single_Posture.json"
46
  )
47
 
48
+ PREINSTALLED_PACKAGES=( "torch" "torchvision" "torchaudio" "numpy" "pillow" "pyyaml" "tqdm" "requests" "opencv-python" "scipy" )
 
 
 
 
 
 
 
 
 
 
 
 
49
  log_info() { echo "--> $1"; }
50
  log_warn() { echo " ⚠️ $1"; }
51
  log_error() { echo " ❌ $1"; }
52
  log_success() { echo " ✅ $1"; }
53
  log_step() { echo ""; echo "=== [Step $1] $2 ==="; }
54
+ package_installed() { $PYTHON_BIN -c "import $1" 2>/dev/null && return 0 || return 1; }
55
+ 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; }
56
+ if [ -f "/workspace/.provision_complete" ]; then log_success "環境已配置,跳過重複執行"; log_info "如需重新配置,請刪除 /workspace/.provision_complete"; exit 0; fi
57
+ if [[ -f /.noprovisioning ]]; then log_warn "檢測到 /.noprovisioning 文件,跳過配置"; exit 0; fi
58
+ 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
59
+ 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 ]; }
60
+ 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 ]; }
61
+ install_node() { local repo_url="$1"; local branch_name="${2:-}"; 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"; if [ -n "$branch_name" ]; then git clone --depth 1 --single-branch --branch "$branch_name" "$repo_url" "$install_path" -q 2>&1 || true; else git clone --depth 1 --single-branch "$repo_url" "$install_path" -q 2>&1 || true; fi; 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; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  download_file() {
64
  local dest_path="$1"
65
  local url="$2"
66
  local filename
67
  filename=$(basename "$dest_path")
68
+ local tmp_path="${dest_path}.tmp"
69
+
70
  mkdir -p "$(dirname "$dest_path")"
71
 
72
+ if [ -s "$dest_path" ]; then
73
+ log_info "檔案 '$filename' 已存在且完整,跳過下載"
74
+ return 0
75
+ fi
76
+
77
+ log_info "下載: $filename"
78
 
79
  local max_retries=3
80
  local attempt=0
81
  local auth_header=""
82
+ local success=1
83
 
84
  if [[ "$url" =~ huggingface\.co ]] && provisioning_has_valid_hf_token; then
85
  auth_header="Authorization: Bearer $HF_TOKEN"
 
96
  if command -v aria2c >/dev/null 2>&1; then
97
  log_info "使用 aria2c (3 線程) 下載: $filename (嘗試 $attempt/$max_retries)"
98
  local aria_opts=(
99
+ --console-log-level=error -c -x 3 -s 3 -k 1M
100
+ --max-connection-per-server=3 --max-tries=3 --retry-wait=5
101
+ --timeout=180 --file-allocation=falloc --auto-file-renaming=false
102
+ -d "$(dirname "$dest_path")" -o "${filename}.tmp"
 
 
 
 
 
 
103
  )
 
104
  [[ -n "$auth_header" ]] && aria_opts+=(--header="$auth_header")
105
 
106
+ aria2c "${aria_opts[@]}" "$url"
107
+ if [ $? -eq 0 ]; then
108
+ success=0
109
+ break
110
+ fi
111
  else
112
  log_info "使用 wget 下載: $filename (嘗試 $attempt/$max_retries)"
113
  local wget_opts=(
114
+ -O "$tmp_path" -c --timeout=60 --tries=3
115
+ --content-disposition --show-progress
 
 
 
116
  )
117
  [[ -n "$auth_header" ]] && wget_opts+=(--header="$auth_header")
118
 
119
+ wget "${wget_opts[@]}" "$url"
120
+ if [ $? -eq 0 ]; then
121
+ success=0
122
+ break
123
+ fi
124
  fi
125
  done
126
 
127
+ if [ "$success" -eq 0 ] && [ -s "$tmp_path" ]; then
128
+ mv "$tmp_path" "$dest_path"
129
+ log_success "下載完成: $filename"
130
  return 0
131
  else
132
  log_error "下載失敗: $filename"
133
+ rm -f "$tmp_path"
134
  return 1
135
  fi
136
  }
137
 
138
+ download_to_directory() {
139
+ local dest_dir="$1"; shift; local urls=("$@")
 
 
 
140
  if [ ${#urls[@]} -eq 0 ]; then return 0; fi
 
 
141
  mkdir -p "$dest_dir"
142
+ log_info "下載 ${#urls[@]} 個文件到 $dest_dir"
143
+ local MAX_PARALLEL=3
144
+ for url in "${urls[@]}"; do
145
+ while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done
146
+ local filename; filename=$(basename "$url" | sed 's/?.*//')
147
+ download_file "${dest_dir}/${filename}" "$url" &
148
+ done
149
+ wait
150
+ }
151
 
152
+ verify_and_retry_downloads() {
153
+ local dest_dir="$1"; shift; local urls=("$@")
154
+ if [ ${#urls[@]} -eq 0 ]; then return 0; fi
155
+ log_info "檢查 $dest_dir 中的文件..."
156
+
157
+ local missing_files=()
158
  for url in "${urls[@]}"; do
159
+ local filename; filename=$(basename "$url" | sed 's/?.*//')
 
160
  local dest_path="${dest_dir}/${filename}"
161
 
162
+ if [ ! -s "$dest_path" ]; then
163
+ log_warn "檔案缺失或不完整: $filename"
164
+ missing_files+=("$url")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  fi
166
  done
167
 
168
+ if [ ${#missing_files[@]} -gt 0 ]; then
169
+ log_warn "發現 ${#missing_files[@]} 個缺失/不完整文件,重新下載..."
170
+ download_to_directory "$dest_dir" "${missing_files[@]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  else
172
+ log_success "所有文件已確認存在且完整"
173
  fi
174
  }
175
 
176
+ provisioning_print_header() { echo ""; echo "##############################################"; echo "# #"; echo "# Provisioning Container #"; echo "# #"; echo "# This will take some time #"; echo "# #"; echo "##############################################"; echo ""; }
177
+ provisioning_print_end() { echo ""; echo "##############################################"; echo "# #"; echo "# Provisioning Complete! #"; echo "# #"; echo "# Total time: $((END_TIME - START_TIME)) seconds"; echo "# #"; echo "##############################################"; echo ""; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  provisioning_step2_nodes() {
180
  log_step "2" "安裝節點與套件"
 
181
  if [ ${#NODES[@]} -gt 0 ]; then
182
  cd "${COMFYUI_DIR}/custom_nodes" || exit 1
183
  log_info "並行安裝 ${#NODES[@]} 個節點..."
 
184
  MAX_PARALLEL=2
185
  for node in "${NODES[@]}"; do
186
+ while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done
 
 
187
  install_node "$node" &
188
  done
189
  wait
 
190
  log_success "節點安裝完成"
191
  cd "${COMFYUI_DIR}" || exit 1
192
  fi
 
193
  if [ ${#PIP_PACKAGES[@]:-} -gt 0 ]; then
194
  log_info "檢查並安裝額外的 Python 套件..."
 
195
  local packages_to_install=()
196
  for pkg in "${PIP_PACKAGES[@]}"; do
197
+ local pkg_name="${pkg/[<>=!].*/}"; local module_name="${pkg_name//-/_}"
198
+ if package_installed "$module_name"; then log_info "跳過已安裝套件: $pkg_name"; else packages_to_install+=("$pkg"); fi
 
 
 
 
 
 
199
  done
200
+ if [ ${#packages_to_install[@]} -gt 0 ]; then $PIP_BIN install -q --no-cache-dir "${packages_to_install[@]}" 2>&1 | grep -v "Requirement already satisfied" || true; fi
 
 
 
201
  fi
202
  }
203
 
204
+ provisioning_step3_downloads() {
205
+ log_step "3" "下載模型與工作流文件"
206
+ download_to_directory "${COMFYUI_DIR}/models/checkpoints" "${CHECKPOINT_MODELS[@]}"
207
+ download_to_directory "${COMFYUI_DIR}/models/clip_vision" "${CLIP_VISION_MODELS[@]}"
208
+ download_to_directory "${COMFYUI_DIR}/models/ipadapter" "${IPADAPTER_MODELS[@]}"
209
+ download_to_directory "${COMFYUI_DIR}/models/sams" "${SAM_MODELS[@]}"
210
+ download_to_directory "${COMFYUI_DIR}/models/ultralytics/bbox" "${ULTRALYTICS_BBOX_MODELS[@]}"
211
+ download_to_directory "${COMFYUI_DIR}/models/upscale_models" "${ESRGAN_MODELS[@]}"
 
 
212
  if [ ${#WORKFLOWS[@]} -gt 0 ]; then
213
+ log_info "下載工作流文件..."
214
+ download_to_directory "${COMFYUI_DIR}/user/default/workflows" "${WORKFLOWS[@]}"
215
  fi
216
  }
217
 
218
  START_TIME=$(date +%s)
219
  log_info "開始時間: $(date)"
220
  provisioning_print_header
 
221
  cd "${COMFYUI_DIR}" || exit 1
222
 
223
+ log_step "1" "安裝系統套件 (aria2)"
224
+ if ! command -v aria2c >/dev/null 2>&1; then
225
+ log_info "aria2 未安裝,開始安裝..."
 
 
 
 
 
 
 
 
 
226
  if command -v sudo &>/dev/null && sudo -n true 2>/dev/null; then
227
+ sudo apt-get update -qq && sudo apt-get install -y -qq aria2 && log_success "aria2 安裝完成" || log_warn "aria2 安裝失敗"
 
 
228
  else
229
+ log_warn "無 sudo 權限,無法安裝 aria2 (下載將使用 wget 備用)"
 
230
  fi
231
  else
232
+ log_success "aria2 已安裝,跳過"
233
  fi
234
 
235
+ provisioning_step2_nodes &
236
+ NODE_PID=$!
237
+ provisioning_step3_downloads
238
+ wait $NODE_PID
239
+
240
+ log_step "4" "驗證下載完整性"
241
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/checkpoints" "${CHECKPOINT_MODELS[@]}"
242
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/clip_vision" "${CLIP_VISION_MODELS[@]}"
243
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/ipadapter" "${IPADAPTER_MODELS[@]}"
244
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/sams" "${SAM_MODELS[@]}"
245
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/ultralytics/bbox" "${ULTRALYTICS_BBOX_MODELS[@]}"
246
+ verify_and_retry_downloads "${COMFYUI_DIR}/models/upscale_models" "${ESRGAN_MODELS[@]}"
247
+ verify_and_retry_downloads "${COMFYUI_DIR}/user/default/workflows" "${WORKFLOWS[@]}"
248
+
249
+ log_step "5" "完成配置"
250
  touch "/workspace/.provision_complete"
251
  log_success "配置標記文件已創建"
252