JCscrew commited on
Commit
32cab06
·
verified ·
1 Parent(s): 77cee93

Update script.sh

Browse files
Files changed (1) hide show
  1. script.sh +211 -42
script.sh CHANGED
@@ -1,5 +1,6 @@
1
  #!/bin/bash
2
  set -uo pipefail
 
3
 
4
  export PIP_NO_CACHE_DIR=1
5
  export PIP_DISABLE_PIP_VERSION_CHECK=1
@@ -7,46 +8,45 @@ export GIT_TERMINAL_PROMPT=0
7
 
8
  source /venv/main/bin/activate
9
  COMFYUI_DIR="${WORKSPACE:-/workspace}/ComfyUI"
 
 
 
 
 
 
 
10
 
11
  APT_PACKAGES=(
12
  "aria2"
13
  )
14
  PIP_PACKAGES=(
15
  )
16
-
17
  NODES=(
18
  "https://github.com/ltdrdata/ComfyUI-Impact-Pack.git"
19
  "https://github.com/ltdrdata/ComfyUI-Impact-Subpack.git"
20
  "https://github.com/cubiq/ComfyUI_IPAdapter_plus.git"
21
  "https://github.com/ka-puna/comfyui-yanc.git"
22
  )
23
-
24
  CHECKPOINT_MODELS=(
25
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/checkpoints/waiNSFWIllustrious_v150.safetensors"
26
  )
27
-
28
  CLIP_VISION_MODELS=(
29
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/clip_vision/CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
30
  )
31
-
32
  IPADAPTER_MODELS=(
33
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus-face_sdxl_vit-h.safetensors"
34
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus_sdxl_vit-h.safetensors"
35
  )
36
-
37
  SAM_MODELS=(
38
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/sams/sam_vit_h_4b8939.pth"
39
  )
40
-
41
  ULTRALYTICS_BBOX_MODELS=(
42
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/face_yolov9c.pt"
43
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/hand_yolov9c.pt"
44
  )
45
-
46
  ESRGAN_MODELS=(
47
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/upscale_models/RealESRGAN_x4plus_anime_6B.pth"
48
  )
49
-
50
  WORKFLOWS=(
51
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/Single_Posture.json"
52
  )
@@ -56,17 +56,197 @@ log_warn() { echo " ⚠️ $1"; }
56
  log_error() { echo " ❌ $1"; }
57
  log_success() { echo " ✅ $1"; }
58
  log_step() { echo ""; echo "=== [Step $1] $2 ==="; }
59
- 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
60
- package_installed() { $PYTHON_BIN -c "import $1" 2>/dev/null && return 0 || return 1; }
61
- 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; }
62
- 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 ]; }
63
- 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 ]; }
64
- 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; }
65
- 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; }
66
- 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; }
67
- 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; }
68
- provisioning_print_header() { echo ""; echo "##############################################"; echo "# #"; echo "# Provisioning Container #"; echo "# #"; echo "# This will take some time #"; echo "# #"; echo "##############################################"; echo ""; }
69
- provisioning_print_end() { echo ""; echo "##############################################"; echo "# #"; echo "# Provisioning Complete! #"; echo "# #"; echo "# Total time: $((END_TIME - START_TIME)) seconds"; echo "# #"; echo "##############################################"; echo ""; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  provisioning_step1_install_core_deps() {
72
  log_step "1" "安裝系統與 ComfyUI 核心依賴"
@@ -82,13 +262,10 @@ provisioning_step1_install_core_deps() {
82
  done
83
  if [ ${#packages_to_install[@]} -gt 0 ]; then
84
  log_info "正在安裝: ${packages_to_install[*]}"
85
- if command -v sudo &>/dev/null && sudo -n true 2>/dev/null; then
86
- sudo apt-get update -qq && sudo apt-get install -y -qq "${packages_to_install[@]}" && log_success "系統套件安裝完成" || log_warn "部分系統套件安裝失敗"
87
- else
88
- log_warn "無 sudo 權限,無法安裝系統套件"
89
- fi
90
  fi
91
  fi
 
92
  local comfyui_req_file="${COMFYUI_DIR}/requirements.txt"
93
  if [ -f "$comfyui_req_file" ]; then
94
  log_info "處理 ComfyUI 核心依賴: $comfyui_req_file"
@@ -106,7 +283,7 @@ provisioning_step2_nodes() {
106
  if [ ${#NODES[@]} -gt 0 ]; then
107
  cd "${COMFYUI_DIR}/custom_nodes" || exit 1
108
  log_info "並行安裝 ${#NODES[@]} 個節點..."
109
- MAX_PARALLEL=2
110
  for node in "${NODES[@]}"; do
111
  while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done
112
  install_node "$node" &
@@ -115,18 +292,6 @@ provisioning_step2_nodes() {
115
  log_success "節點安裝完成"
116
  cd "${COMFYUI_DIR}" || exit 1
117
  fi
118
- if [ ${#PIP_PACKAGES[@]} -gt 0 ]; then
119
- log_info "檢查並安裝額外的 Python 套件..."
120
- local packages_to_install=()
121
- for pkg in "${PIP_PACKAGES[@]}"; do
122
- local pkg_name="${pkg/[<>=!].*/}"; local module_name="${pkg_name//-/_}"
123
- if package_installed "$module_name"; then log_info "跳過已安裝套件: $pkg_name"; else packages_to_install+=("$pkg"); fi
124
- done
125
- if [ ${#packages_to_install[@]} -gt 0 ]; then
126
- log_info "正在安裝額外套件: ${packages_to_install[*]}"
127
- $PIP_BIN install -q --no-cache-dir "${packages_to_install[@]}" 2>&1 | grep -v "Requirement already satisfied" || true;
128
- fi
129
- fi
130
  }
131
 
132
  provisioning_step3_downloads() {
@@ -143,11 +308,17 @@ provisioning_step3_downloads() {
143
  fi
144
  }
145
 
146
- if [ -f "/workspace/.provision_complete" ]; then log_success "環境已配置,跳過重複執行"; log_info "如需重新配置,請刪除 /workspace/.provision_complete"; exit 0; fi
147
- if [[ -f /.noprovisioning ]]; then log_warn "檢測到 /.noprovisioning 文件,跳過配置"; exit 0; fi
 
 
 
 
 
 
 
148
 
149
  START_TIME=$(date +%s)
150
- log_info "開始時間: $(date)"
151
  provisioning_print_header
152
  cd "${COMFYUI_DIR}" || exit 1
153
 
@@ -172,8 +343,6 @@ touch "/workspace/.provision_complete"
172
  log_success "配置標記文件已創建"
173
 
174
  END_TIME=$(date +%s)
175
- log_info "結束時間: $(date)"
176
  provisioning_print_end
177
-
178
  log_success "所有配置步驟已完成!"
179
  exit 0
 
1
  #!/bin/bash
2
  set -uo pipefail
3
+ set +H
4
 
5
  export PIP_NO_CACHE_DIR=1
6
  export PIP_DISABLE_PIP_VERSION_CHECK=1
 
8
 
9
  source /venv/main/bin/activate
10
  COMFYUI_DIR="${WORKSPACE:-/workspace}/ComfyUI"
11
+ if [ -f "/venv/main/bin/python" ]; then
12
+ PYTHON_BIN="/venv/main/bin/python"
13
+ PIP_BIN="/venv/main/bin/pip"
14
+ else
15
+ PYTHON_BIN="python3"
16
+ PIP_BIN="pip3"
17
+ fi
18
 
19
  APT_PACKAGES=(
20
  "aria2"
21
  )
22
  PIP_PACKAGES=(
23
  )
 
24
  NODES=(
25
  "https://github.com/ltdrdata/ComfyUI-Impact-Pack.git"
26
  "https://github.com/ltdrdata/ComfyUI-Impact-Subpack.git"
27
  "https://github.com/cubiq/ComfyUI_IPAdapter_plus.git"
28
  "https://github.com/ka-puna/comfyui-yanc.git"
29
  )
 
30
  CHECKPOINT_MODELS=(
31
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/checkpoints/waiNSFWIllustrious_v150.safetensors"
32
  )
 
33
  CLIP_VISION_MODELS=(
34
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/clip_vision/CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
35
  )
 
36
  IPADAPTER_MODELS=(
37
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus-face_sdxl_vit-h.safetensors"
38
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ipadapter/ip-adapter-plus_sdxl_vit-h.safetensors"
39
  )
 
40
  SAM_MODELS=(
41
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/sams/sam_vit_h_4b8939.pth"
42
  )
 
43
  ULTRALYTICS_BBOX_MODELS=(
44
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/face_yolov9c.pt"
45
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/ultralytics/bbox/hand_yolov9c.pt"
46
  )
 
47
  ESRGAN_MODELS=(
48
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/models/upscale_models/RealESRGAN_x4plus_anime_6B.pth"
49
  )
 
50
  WORKFLOWS=(
51
  "https://huggingface.co/JCscrew/RisuAI_asset_generator/resolve/main/Single_Posture.json"
52
  )
 
56
  log_error() { echo " ❌ $1"; }
57
  log_success() { echo " ✅ $1"; }
58
  log_step() { echo ""; echo "=== [Step $1] $2 ==="; }
59
+
60
+ package_installed() {
61
+ $PYTHON_BIN -c "import $1" 2>/dev/null && return 0 || return 1
62
+ }
63
+
64
+ filter_requirements() {
65
+ local req_file="$1"
66
+ local tmp_file="${req_file}.filtered"
67
+ > "$tmp_file"
68
+ while IFS= read -r line; do
69
+ [[ -z "$line" || "$line" =~ ^# ]] && continue
70
+ local pkg_name
71
+ pkg_name=$(echo "$line" | sed 's/[<>=!].*//' | xargs)
72
+ if [[ -z "$pkg_name" ]]; then continue; fi
73
+ local module_name="${pkg_name//-/_}"
74
+ if package_installed "$module_name"; then
75
+ log_info "跳過已安裝套件: $pkg_name"
76
+ else
77
+ echo "$line" >> "$tmp_file"
78
+ fi
79
+ done < "$req_file"
80
+ if [ -s "$tmp_file" ]; then
81
+ mv "$tmp_file" "$req_file"
82
+ return 0
83
+ else
84
+ rm -f "$tmp_file"
85
+ log_info "所有套件都已安裝"
86
+ return 1
87
+ fi
88
+ }
89
+
90
+ provisioning_has_valid_hf_token() {
91
+ [[ -n "${HF_TOKEN:-}" ]] || return 1
92
+ local response
93
+ 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")
94
+ [ "$response" -eq 200 ]
95
+ }
96
+
97
+ provisioning_has_valid_civitai_token() {
98
+ [[ -n "${CIVITAI_TOKEN:-}" ]] || return 1
99
+ local response
100
+ 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")
101
+ [ "$response" -eq 200 ]
102
+ }
103
+
104
+ install_node() {
105
+ local repo_url="$1"
106
+ local repo_name
107
+ repo_name=$(basename "$repo_url" .git)
108
+ local install_path="${COMFYUI_DIR}/custom_nodes/${repo_name}"
109
+
110
+ if [ -d "$install_path" ]; then
111
+ if [[ "${AUTO_UPDATE:-true}" != "false" ]]; then
112
+ log_info "更新節點: $repo_name"
113
+ (cd "$install_path" && git pull -q 2>&1 | grep -v "Already up to date" || true)
114
+ else
115
+ log_info "'$repo_name' 已存在,跳過"
116
+ return
117
+ fi
118
+ else
119
+ log_info "克隆節點: $repo_name"
120
+ git clone --depth 1 --single-branch "$repo_url" "$install_path" -q 2>&1 || true
121
+ fi
122
+
123
+ if [ -f "$install_path/requirements.txt" ]; then
124
+ log_info "處理 $repo_name 的依賴..."
125
+ sed -i -e '/^torch/d' -e '/^sam2/d' "$install_path/requirements.txt" 2>/dev/null || true
126
+ if filter_requirements "$install_path/requirements.txt"; then
127
+ log_info "安裝 $repo_name 的新依賴..."
128
+ $PIP_BIN install -q --no-cache-dir -r "$install_path/requirements.txt" 2>&1 | grep -v "Requirement already satisfied" || log_warn "部分依賴安裝失敗"
129
+ fi
130
+ fi
131
+
132
+ if [ -f "$install_path/install.py" ]; then
133
+ log_info "運行 $repo_name 的安裝腳本..."
134
+ $PYTHON_BIN "$install_path/install.py" 2>&1 || log_warn "安裝腳本執行失敗"
135
+ fi
136
+ }
137
+
138
+ download_file() {
139
+ local dest_path="$1"
140
+ local url="$2"
141
+ local filename
142
+ filename=$(basename "$dest_path")
143
+ local tmp_path="${dest_path}.tmp"
144
+
145
+ mkdir -p "$(dirname "$dest_path")"
146
+ if [ -s "$dest_path" ]; then
147
+ log_info "檔案 '$filename' 已存在且完整,跳過下載"
148
+ return 0
149
+ fi
150
+
151
+ log_info "下載: $filename"
152
+ local max_retries=3
153
+ local attempt=0
154
+ local auth_header=""
155
+ local success=1
156
+
157
+ if [[ "$url" =~ huggingface\.co ]] && provisioning_has_valid_hf_token; then
158
+ auth_header="Authorization: Bearer $HF_TOKEN"
159
+ log_info "使用 HuggingFace Token"
160
+ elif [[ "$url" =~ civitai\.com ]] && provisioning_has_valid_civitai_token; then
161
+ auth_header="Authorization: Bearer $CIVITAI_TOKEN"
162
+ log_info "使用 CivitAI Token"
163
+ fi
164
+
165
+ while [ "$attempt" -lt "$max_retries" ]; do
166
+ attempt=$((attempt + 1))
167
+ [ "$attempt" -gt 1 ] && sleep 10
168
+ if command -v aria2c >/dev/null 2>&1; then
169
+ log_info "使用 aria2c (3 線程) 下載: $filename (嘗試 $attempt/$max_retries)"
170
+ 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")
171
+ [[ -n "$auth_header" ]] && aria_opts+=(--header="$auth_header")
172
+ aria2c "${aria_opts[@]}" "$url"
173
+ if [ $? -eq 0 ]; then success=0; break; fi
174
+ else
175
+ log_info "使用 wget 下載: $filename (嘗試 $attempt/$max_retries)"
176
+ local wget_opts=(-O "$tmp_path" -c --timeout=60 --tries=3 --content-disposition --show-progress)
177
+ [[ -n "$auth_header" ]] && wget_opts+=(--header="$auth_header")
178
+ wget "${wget_opts[@]}" "$url"
179
+ if [ $? -eq 0 ]; then success=0; break; fi
180
+ fi
181
+ done
182
+
183
+ if [ "$success" -eq 0 ] && [ -s "$tmp_path" ]; then
184
+ mv "$tmp_path" "$dest_path"
185
+ log_success "下載完成: $filename"
186
+ return 0
187
+ else
188
+ log_error "下載失敗: $filename"
189
+ rm -f "$tmp_path"
190
+ return 1
191
+ fi
192
+ }
193
+
194
+ download_to_directory() {
195
+ local dest_dir="$1"; shift; local urls=("$@")
196
+ if [ ${#urls[@]} -eq 0 ]; then return 0; fi
197
+ mkdir -p "$dest_dir"
198
+ log_info "下載 ${#urls[@]} 個文件到 $dest_dir"
199
+ local MAX_PARALLEL=3
200
+ for url in "${urls[@]}"; do
201
+ while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done
202
+ local filename
203
+ filename=$(basename "$url" | sed 's/?.*//')
204
+ download_file "${dest_dir}/${filename}" "$url" &
205
+ done
206
+ wait
207
+ }
208
+
209
+ verify_and_retry_downloads() {
210
+ local dest_dir="$1"; shift; local urls=("$@")
211
+ if [ ${#urls[@]} -eq 0 ]; then return 0; fi
212
+ log_info "檢查 $dest_dir 中的文件..."
213
+ local missing_files=()
214
+ for url in "${urls[@]}"; do
215
+ local filename
216
+ filename=$(basename "$url" | sed 's/?.*//')
217
+ local dest_path="${dest_dir}/${filename}"
218
+ if [ ! -s "$dest_path" ]; then
219
+ log_warn "檔案缺失或不完整: $filename"
220
+ missing_files+=("$url")
221
+ fi
222
+ done
223
+ if [ ${#missing_files[@]} -gt 0 ]; then
224
+ log_warn "發現 ${#missing_files[@]} 個缺失/不完整文件,重新下載..."
225
+ download_to_directory "$dest_dir" "${missing_files[@]}"
226
+ else
227
+ log_success "所有文件已確認存在且完整"
228
+ fi
229
+ }
230
+
231
+ provisioning_print_header() {
232
+ echo ""; echo "##############################################"
233
+ echo "# #"
234
+ echo "# Provisioning Container #"
235
+ echo "# #"
236
+ echo "# This will take some time #"
237
+ echo "# #"
238
+ echo "##############################################"; echo ""
239
+ }
240
+
241
+ provisioning_print_end() {
242
+ echo ""; echo "##############################################"
243
+ echo "# #"
244
+ echo "# Provisioning Complete! #"
245
+ echo "# #"
246
+ echo "# Total time: $((END_TIME - START_TIME)) seconds #"
247
+ echo "# #"
248
+ echo "##############################################"; echo ""
249
+ }
250
 
251
  provisioning_step1_install_core_deps() {
252
  log_step "1" "安裝系統與 ComfyUI 核心依賴"
 
262
  done
263
  if [ ${#packages_to_install[@]} -gt 0 ]; then
264
  log_info "正在安裝: ${packages_to_install[*]}"
265
+ apt-get update -qq && apt-get install -y -qq "${packages_to_install[@]}" && log_success "系統套件安裝完成" || log_warn "部分系統套件安裝失敗"
 
 
 
 
266
  fi
267
  fi
268
+
269
  local comfyui_req_file="${COMFYUI_DIR}/requirements.txt"
270
  if [ -f "$comfyui_req_file" ]; then
271
  log_info "處理 ComfyUI 核心依賴: $comfyui_req_file"
 
283
  if [ ${#NODES[@]} -gt 0 ]; then
284
  cd "${COMFYUI_DIR}/custom_nodes" || exit 1
285
  log_info "並行安裝 ${#NODES[@]} 個節點..."
286
+ local MAX_PARALLEL=2
287
  for node in "${NODES[@]}"; do
288
  while [ $(jobs -r | wc -l) -ge $MAX_PARALLEL ]; do sleep 1; done
289
  install_node "$node" &
 
292
  log_success "節點安裝完成"
293
  cd "${COMFYUI_DIR}" || exit 1
294
  fi
 
 
 
 
 
 
 
 
 
 
 
 
295
  }
296
 
297
  provisioning_step3_downloads() {
 
308
  fi
309
  }
310
 
311
+ if [ -f "/workspace/.provision_complete" ]; then
312
+ log_success "環境已配置,跳過重複執行"
313
+ log_info "如需重新配置,請刪除 /workspace/.provision_complete"
314
+ exit 0
315
+ fi
316
+ if [[ -f /.noprovisioning ]]; then
317
+ log_warn "檢測到 /.noprovisioning 文件,跳過配置"
318
+ exit 0
319
+ fi
320
 
321
  START_TIME=$(date +%s)
 
322
  provisioning_print_header
323
  cd "${COMFYUI_DIR}" || exit 1
324
 
 
343
  log_success "配置標記文件已創建"
344
 
345
  END_TIME=$(date +%s)
 
346
  provisioning_print_end
 
347
  log_success "所有配置步驟已完成!"
348
  exit 0