cocount21 commited on
Commit
fa418c4
·
verified ·
1 Parent(s): fb52eb4

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +206 -0
  2. provision.sh +388 -0
  3. workflows/darkHUB-Flux-FaceSwap.json +0 -0
README.md ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - comfyui
5
+ - runpod
6
+ - faceswap
7
+ ---
8
+
9
+ # ComfyUI FaceSwap — готовый темплейт для RunPod
10
+
11
+ Поднимает на RunPod полностью настроенный ComfyUI с пайплайном замены лица:
12
+
13
+ 1. **Flux.2 Klein 9b** — переносит голову с одного фото на другое, сохраняя свет, ракурс и пропорции
14
+ 2. **Z-Image Turbo + FaceDetailer** — заново отрисовывает лицо в высоком разрешении, сюда подключается персональная LoRA
15
+
16
+ Оба этапа работают в связке и оба обязательны. Flux хорошо ставит голову по свету и ракурсу, но лицо у него выходит лишь похожим. Z-Image затем вырезает это лицо и рисует его заново — уже с обученной на конкретном человеке LoRA. Отсюда и настоящее сходство.
17
+
18
+ Ничего устанавливать руками не нужно. Один раз создаёшь темплейт — дальше запуск в два клика.
19
+
20
+ **Железо:** проверено на L40S (48 ГБ VRAM). На 24 ГБ тоже должно пойти, но не тестировалось.
21
+
22
+ ---
23
+
24
+ ## Что нужно иметь
25
+
26
+ - аккаунт на [RunPod](https://runpod.io) с пополненным балансом
27
+ - аккаунт на [HuggingFace](https://huggingface.co) — бесплатный
28
+
29
+ ---
30
+
31
+ ## Шаг 1. Токен HuggingFace
32
+
33
+ Нужен, чтобы скачать модель Flux. Свой, личный — чужие токены не используй.
34
+
35
+ 1. Открой https://huggingface.co/settings/tokens
36
+ 2. **Create new token**
37
+ 3. Тип — **Read** (не Write)
38
+ 4. Название любое, например `runpod`
39
+ 5. **Create token** → скопируй и сохрани в блокнот
40
+
41
+ HuggingFace показывает токен только один раз. Потеряешь — сделаешь новый, ничего страшного.
42
+
43
+ ---
44
+
45
+ ## Шаг 2. Принять лицензию Flux
46
+
47
+ 1. Открой https://huggingface.co/black-forest-labs/FLUX.2-klein-9b-fp8
48
+ 2. Убедись, что залогинен
49
+ 3. Нажми **Agree** на серой плашке
50
+
51
+ Выбирать там нечего, полей нет — просто кнопка.
52
+
53
+ > ⚠️ У FLUX.2 Klein **некоммерческая лицензия**. Использование результатов в коммерческих целях она не разрешает. Это твоя зона ответственности.
54
+ >
55
+ > Обойти этот шаг и всё равно пользоваться воркфлоу не получится: Flux здесь делает первый этап, без него замена лица не работает. Переключатель `WANT_FLUX=0` из таблицы настроек существует для тех, кто собирает что-то своё только на Z-Image — для этого воркфлоу его выключать нельзя.
56
+
57
+ ---
58
+
59
+ ## Шаг 3. Создать темплейт в RunPod
60
+
61
+ Консоль RunPod → левое меню **Templates** → **New Template**.
62
+
63
+ | Поле | Значение |
64
+ |---|---|
65
+ | Template Name | `ComfyUI-FaceSwap` |
66
+ | Template Type | `Pod` |
67
+ | Container Image | `runpod/pytorch:1.3.1-cu1281-torch291-ubuntu2404` |
68
+ | Container Disk | `20` GB |
69
+ | Volume Disk | `80` GB |
70
+ | Volume Mount Path | `/workspace` |
71
+ | Expose HTTP Ports | `8188,8888` |
72
+ | Expose TCP Ports | `22` |
73
+
74
+ **Container Start Command** (в некоторых версиях поле называется Docker Command):
75
+
76
+ ```
77
+ bash -c "curl -sL https://huggingface.co/cocount21/comfy-faceswap-setup/resolve/main/provision.sh -o /provision.sh && bash /provision.sh 2>&1 | tee /workspace/provision.log"
78
+ ```
79
+
80
+ **Environment Variables** — добавь одну переменную:
81
+
82
+ | Key | Value |
83
+ |---|---|
84
+ | `HF_TOKEN` | **оставь пустым** |
85
+
86
+ Пустым — потому что заполнять её ты будешь при запуске пода, своим токеном. Так она видна в форме деплоя и её не забудешь.
87
+
88
+ Нажми **Save Template**.
89
+
90
+ ---
91
+
92
+ ## Шаг 4. Запустить
93
+
94
+ 1. **Pods** → **Deploy**
95
+ 2. Выбери **L40S**
96
+ 3. **Change Template** → свой `ComfyUI-FaceSwap`
97
+ 4. В блоке Environment Variables впиши **свой** токен в `HF_TOKEN`
98
+ 5. **Deploy On-Demand**
99
+
100
+ ---
101
+
102
+ ## Шаг 5. Подождать
103
+
104
+ Первый запуск — 10–20 минут, качается около 40 ГБ моделей. ComfyUI стартует последним, раньше времени его дёргать бесполезно.
105
+
106
+ Смотреть прогресс: карточка пода → **Logs** → **Container Logs**.
107
+
108
+ В конце скрипт печатает отчёт:
109
+
110
+ ```
111
+ =========================== SUMMARY ===========================
112
+ OK Flux-2-Klein-9b-fp8.safetensors
113
+ OK qwen_3_8b_fp8mixed.safetensors
114
+ ...
115
+ =================================================================
116
+ time: 812 s
117
+
118
+ ALL GOOD - ComfyUI is starting on port 8188
119
+ ```
120
+
121
+ Зелёное `ALL GOOD` — всё готово. Красные строки — смотри раздел «Если что-то пошло не так».
122
+
123
+ ---
124
+
125
+ ## Шаг 6. Работать
126
+
127
+ 1. Карточка пода → **Connect** → **HTTP Service → :8188**
128
+ 2. Воркфлоу уже загружен — в левой панели ComfyUI, раздел Workflows
129
+ 3. Загрузи две картинки:
130
+ - группа **Your-Image** — фото-основа: тело, поза, фон
131
+ - группа **Reference-Image** — лицо, которое переносим
132
+ 4. **Run**
133
+
134
+ Результат — в ноде **SaveImage** с жёлтой рамкой. Вторая SaveImage показывает промежуточный результат Flux, до починки лица.
135
+
136
+ ### Своя LoRA
137
+
138
+ Залей свою LoRA в любой публичный репозиторий на HuggingFace и при деплое добавь переменную:
139
+
140
+ ```
141
+ EXTRA_LORAS = твой_ник/твой_репозиторий:имя_файла.safetensors
142
+ ```
143
+
144
+ Несколько — через запятую. Файлы лягут в `models/loras/`, останется выбрать их в ноде.
145
+
146
+ ---
147
+
148
+ ## Настройки (Environment Variables)
149
+
150
+ | Переменная | По умолчанию | Что делает |
151
+ |---|---|---|
152
+ | `HF_TOKEN` | пусто | твой read-токен HuggingFace |
153
+ | `WANT_FLUX` | `1` | этап Flux.2 Klein, ~19 ГБ, gated, non-commercial. **Для этого воркфлоу выключать нельзя** |
154
+ | `WANT_ZIMAGE` | `1` | этап Z-Image Turbo, ~21 ГБ, Apache 2.0. **Для этого воркфлоу выключать нельзя** |
155
+ | `WANT_DETECTORS` | `1` | детекторы лица для FaceDetailer, ~0.4 ГБ. **Выключать нельзя** |
156
+ | `EXTRA_LORAS` | пусто | свои LoRA: `repo:file.safetensors`, через запятую |
157
+ | `GET_WORKFLOWS` | `1` | подтянуть воркфлоу автоматически |
158
+ | `START_JUPYTER` | `0` | поднять JupyterLab на порту 8888 |
159
+ | `SKIP_MODELS` | `0` | только ноды, без моделей — для быстрой проверки |
160
+ | `COMFY_PORT` | `8188` | порт ComfyUI |
161
+ | `COMFY_REF` | пусто | закрепить ComfyUI на конкретном коммите |
162
+
163
+ ---
164
+
165
+ ## Если что-то пошло не так
166
+
167
+ Скрипт проверяет доступ к Flux **до** скачивания и объясняет проблему прямо в логе.
168
+
169
+ | В логе | Причина | Решение |
170
+ |---|---|---|
171
+ | `HTTP 401` | токен неверный или пустой | сделать Read-токен, вписать в `HF_TOKEN` |
172
+ | `HTTP 403` | лицензия не принята | нажать Agree на странице модели |
173
+ | `BAD SIZE` | файл скачался битым | перезапустить под, скрипт перекачает сам |
174
+ | `TORCH CHANGED` | нода подменила torch | сообщи автору темплейта |
175
+ | `NODE MISSING` | не склонировалась нода | перезапустить под |
176
+
177
+ Полный лог лежит в поде: `/workspace/provision.log`
178
+
179
+ ---
180
+
181
+ ## Что внутри
182
+
183
+ **Ноды:** ComfyUI-Manager, Impact-Pack, Impact-Subpack, Easy-Use, rgthree-comfy, utils-nodes
184
+
185
+ **Модели:**
186
+
187
+ | Файл | Размер | Лицензия |
188
+ |---|---|---|
189
+ | Flux-2-Klein-9b-fp8 | 8.8 ГБ | FLUX Non-Commercial |
190
+ | qwen_3_8b_fp8mixed | 8.1 ГБ | — |
191
+ | flux2-vae | 0.3 ГБ | — |
192
+ | bfs_head_v1 (LoRA свапа) | 0.6 ГБ | не указана автором |
193
+ | z_image_turbo_bf16 | 11.5 ГБ | Apache 2.0 |
194
+ | qwen_3_4b | 7.5 ГБ | Apache 2.0 |
195
+ | ae (VAE) | 0.3 ГБ | Apache 2.0 |
196
+ | face_yolov8m + SAM | 0.4 ГБ | — |
197
+
198
+ Скрипт сверяет размер каждого скачанного файла с эталоном. Битый файл будет пойман сразу, а не через час непонятных ошибок.
199
+
200
+ ---
201
+
202
+ ## Безопасность
203
+
204
+ - Токен вписывай **свой**. Переменные окружения видны изнутри пода любому, у кого есть к нему доступ.
205
+ - Используй **Read**-токен, не Write. Read не даёт ничего испортить.
206
+ - Не вставляй токен в сам скрипт — только в переменную окружения.
provision.sh ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # ===========================================================================
3
+ # ComfyUI provisioning for RunPod
4
+ # Flux.2 Klein head-swap + Z-Image Turbo face restore
5
+ # ===========================================================================
6
+ #
7
+ # RunPod template -> "Container Start Command":
8
+ # bash -c "curl -sL https://huggingface.co/cocount21/comfy-faceswap-setup/resolve/main/provision.sh -o /provision.sh && bash /provision.sh 2>&1 | tee /workspace/provision.log"
9
+ #
10
+ # ------------------------------- SETTINGS -------------------------------
11
+ # HF_TOKEN your OWN HuggingFace read token.
12
+ # Needed only when WANT_FLUX=1 (Klein 9b is a gated repo).
13
+ # Leave it EMPTY in the shared template - every user
14
+ # types in their own at deploy time.
15
+ #
16
+ # WANT_FLUX=1 Flux.2 Klein 9b stage (~19 GB, gated, non-commercial)
17
+ # WANT_ZIMAGE=1 Z-Image Turbo stage (~21 GB, Apache-2.0, open)
18
+ # WANT_DETECTORS=1 face detector + SAM (~0.4 GB, needed by FaceDetailer)
19
+ #
20
+ # EXTRA_LORAS="" your own LoRAs, comma separated, "repo:path-in-repo"
21
+ # e.g. EXTRA_LORAS="myname/myrepo:my_lora.safetensors"
22
+ #
23
+ # GET_WORKFLOWS=1 auto-install the .json workflows into ComfyUI
24
+ # START_JUPYTER=0 also run JupyterLab on :8888
25
+ # SKIP_MODELS=0 set up nodes only, download nothing
26
+ # COMFY_PORT=8188
27
+ # COMFY_REF="" pin ComfyUI to a git commit/tag instead of latest
28
+ # ===========================================================================
29
+ set -uo pipefail
30
+
31
+ ROOT="${WORKSPACE:-/workspace}"
32
+ COMFY="$ROOT/ComfyUI"
33
+ STAGE="$ROOT/.dl"
34
+ CONS="$ROOT/.torch-constraints.txt"
35
+ M="$COMFY/models"
36
+ PORT="${COMFY_PORT:-8188}"
37
+
38
+ WANT_FLUX="${WANT_FLUX:-1}"
39
+ WANT_ZIMAGE="${WANT_ZIMAGE:-1}"
40
+ WANT_DETECTORS="${WANT_DETECTORS:-1}"
41
+ EXTRA_LORAS="${EXTRA_LORAS:-}"
42
+ GET_WORKFLOWS="${GET_WORKFLOWS:-1}"
43
+ SETUP_REPO="${SETUP_REPO:-cocount21/comfy-faceswap-setup}"
44
+
45
+ GATED_REPO="black-forest-labs/FLUX.2-klein-9b-fp8"
46
+ GATED_FILE="flux-2-klein-9b-fp8.safetensors"
47
+
48
+ REPORT=()
49
+ PROBLEMS=0
50
+
51
+ log() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
52
+ ok() { printf ' \033[1;32m%s\033[0m\n' "$*"; }
53
+ warn() { printf ' \033[1;33m[warn] %s\033[0m\n' "$*"; }
54
+ err() { printf ' \033[1;31m[ERROR] %s\033[0m\n' "$*"; PROBLEMS=$((PROBLEMS+1)); }
55
+ die() { printf '\n\033[1;31m[fail] %s\033[0m\n' "$*"; exit 1; }
56
+ banner() {
57
+ printf '\n\033[1;31m'
58
+ printf '#%.0s' {1..74}; printf '\n'
59
+ while IFS= read -r l; do printf '# %-69s#\n' "$l"; done <<< "$1"
60
+ printf '#%.0s' {1..74}; printf '\033[0m\n\n'
61
+ }
62
+
63
+ mkdir -p "$ROOT"; cd "$ROOT"
64
+ START_TS=$(date +%s)
65
+
66
+ # --------------------------------------------------------------- ssh -------
67
+ if [ -n "${PUBLIC_KEY:-}" ]; then
68
+ mkdir -p ~/.ssh && chmod 700 ~/.ssh
69
+ echo "$PUBLIC_KEY" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
70
+ ssh-keygen -A >/dev/null 2>&1
71
+ /usr/sbin/sshd >/dev/null 2>&1 && ok "sshd up"
72
+ fi
73
+
74
+ # ==========================================================================
75
+ # PREFLIGHT - check the gated repo BEFORE downloading tens of gigabytes
76
+ # ==========================================================================
77
+ if [ "${SKIP_MODELS:-0}" != "1" ] && [ "$WANT_FLUX" = "1" ]; then
78
+ log "Preflight: access to the gated Flux repo"
79
+ CODE=$(curl -s -o /dev/null -w '%{http_code}' -L -r 0-0 \
80
+ -H "Authorization: Bearer ${HF_TOKEN:-nope}" \
81
+ "https://huggingface.co/$GATED_REPO/resolve/main/$GATED_FILE" 2>/dev/null)
82
+ case "$CODE" in
83
+ 200|206)
84
+ ok "access confirmed (HTTP $CODE)" ;;
85
+ 401)
86
+ banner "NO ACCESS TO THE FLUX MODEL (HTTP 401 - bad or missing token)
87
+
88
+ Fix, takes 2 minutes:
89
+
90
+ 1. Create a HuggingFace account: https://huggingface.co/join
91
+ 2. Make a READ token: https://huggingface.co/settings/tokens
92
+ Create new token -> type Read -> copy it
93
+ 3. Put it into this pod's HF_TOKEN environment variable and restart.
94
+
95
+ Skipping the Flux stage for now - Z-Image will still be installed."
96
+ WANT_FLUX=0; PROBLEMS=$((PROBLEMS+1)) ;;
97
+ 403)
98
+ banner "FLUX LICENCE NOT ACCEPTED (HTTP 403)
99
+
100
+ Your token works, but you have not accepted the model licence yet.
101
+
102
+ 1. Open (logged in):
103
+ https://huggingface.co/black-forest-labs/FLUX.2-klein-9b-fp8
104
+ 2. Press the Agree button on the grey banner.
105
+ 3. Restart this pod.
106
+
107
+ Note: FLUX.2 Klein is under a NON-COMMERCIAL licence.
108
+
109
+ Skipping the Flux stage for now - Z-Image will still be installed."
110
+ WANT_FLUX=0; PROBLEMS=$((PROBLEMS+1)) ;;
111
+ *)
112
+ warn "unexpected HTTP $CODE - will try anyway" ;;
113
+ esac
114
+ fi
115
+
116
+ # ------------------------------------------------------------ apt deps -----
117
+ if [ ! -f "$ROOT/.apt-done" ]; then
118
+ log "System packages"
119
+ export DEBIAN_FRONTEND=noninteractive
120
+ apt-get update -qq >/dev/null 2>&1
121
+ apt-get install -y -qq git aria2 libgl1 libglib2.0-0t64 >/dev/null 2>&1 \
122
+ || apt-get install -y -qq git aria2 libgl1 libglib2.0-0 >/dev/null 2>&1 \
123
+ || warn "apt partially failed (opencv-based nodes may complain)"
124
+ touch "$ROOT/.apt-done"; ok "done"
125
+ fi
126
+
127
+ # ------------------------------------------------- pin torch, get hf cli ---
128
+ log "Python tooling"
129
+ python - > "$CONS" <<'PYEOF'
130
+ import torch
131
+ print("torch==" + torch.__version__)
132
+ for m in ("torchvision", "torchaudio"):
133
+ try:
134
+ print(m + "==" + __import__(m).__version__)
135
+ except Exception:
136
+ pass
137
+ PYEOF
138
+ ok "torch pinned: $(head -1 "$CONS")"
139
+
140
+ pip install -q -U pip >/dev/null 2>&1
141
+ pip install -q -U "huggingface_hub[hf_transfer,cli]" >/dev/null 2>&1
142
+ export HF_HUB_ENABLE_HF_TRANSFER=1
143
+ HF_BIN="$(command -v hf || command -v huggingface-cli || true)"
144
+ [ -n "$HF_BIN" ] || die "huggingface cli not found"
145
+ ok "hf cli ready"
146
+
147
+ # -------------------------------------------------------------- ComfyUI ---
148
+ if [ ! -d "$COMFY/.git" ]; then
149
+ log "Cloning ComfyUI"
150
+ if [ -n "${COMFY_REF:-}" ]; then
151
+ git clone https://github.com/comfyanonymous/ComfyUI "$COMFY" >/dev/null 2>&1 \
152
+ && git -C "$COMFY" checkout "$COMFY_REF" >/dev/null 2>&1 \
153
+ && ok "pinned to $COMFY_REF" || die "clone/checkout failed"
154
+ else
155
+ git clone --depth 1 https://github.com/comfyanonymous/ComfyUI "$COMFY" >/dev/null 2>&1 \
156
+ || die "clone failed"
157
+ fi
158
+ fi
159
+ log "ComfyUI requirements"
160
+ pip install -q -r "$COMFY/requirements.txt" -c "$CONS" >/dev/null 2>&1 || warn "some reqs failed"
161
+ ok "done"
162
+
163
+ # --------------------------------------------------------- custom nodes ---
164
+ NODES=(
165
+ "https://github.com/ltdrdata/ComfyUI-Manager"
166
+ "https://github.com/ltdrdata/ComfyUI-Impact-Pack"
167
+ "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
168
+ "https://github.com/yolain/ComfyUI-Easy-Use"
169
+ "https://github.com/rgthree/rgthree-comfy"
170
+ "https://github.com/zhangp365/ComfyUI-utils-nodes"
171
+ )
172
+ log "Custom nodes"
173
+ mkdir -p "$COMFY/custom_nodes"
174
+ for url in "${NODES[@]}"; do
175
+ name="$(basename "$url")"
176
+ dir="$COMFY/custom_nodes/$name"
177
+ if [ ! -d "$dir/.git" ]; then
178
+ if git clone --depth 1 "$url" "$dir" >/dev/null 2>&1; then
179
+ ok "cloned $name"
180
+ else
181
+ err "clone failed: $name"; REPORT+=("NODE MISSING $name"); continue
182
+ fi
183
+ else
184
+ ok "present $name"
185
+ fi
186
+ if [ -f "$dir/requirements.txt" ]; then
187
+ pip install -q -r "$dir/requirements.txt" -c "$CONS" >/dev/null 2>&1 || warn "pip failed: $name"
188
+ fi
189
+ if [ -f "$dir/install.py" ]; then
190
+ ( cd "$dir" && python install.py >/dev/null 2>&1 ) || warn "install.py failed: $name"
191
+ fi
192
+ done
193
+
194
+ TORCH_NOW="$(python -c 'import torch; print(torch.__version__)' 2>/dev/null || echo BROKEN)"
195
+ if [ "torch==$TORCH_NOW" = "$(head -1 "$CONS")" ]; then
196
+ ok "torch intact ($TORCH_NOW)"
197
+ else
198
+ err "TORCH CHANGED -> $TORCH_NOW (expected $(head -1 "$CONS"))"
199
+ REPORT+=("TORCH was replaced by a custom node - generation may fail")
200
+ fi
201
+
202
+ # ============================================================== models =====
203
+ mkdir -p "$M/diffusion_models" "$M/text_encoders" "$M/vae" "$M/loras" "$M/sams" "$M/ultralytics/bbox"
204
+
205
+ hf_size() { # repo path -> expected bytes (0 if unknown)
206
+ python - "$1" "$2" <<'PYEOF'
207
+ import sys, os, json, urllib.request
208
+ repo, path = sys.argv[1], sys.argv[2]
209
+ try:
210
+ req = urllib.request.Request(
211
+ "https://huggingface.co/api/models/%s/tree/main?recursive=true" % repo)
212
+ tok = os.environ.get("HF_TOKEN")
213
+ if tok:
214
+ req.add_header("Authorization", "Bearer " + tok)
215
+ for e in json.load(urllib.request.urlopen(req, timeout=30)):
216
+ if e.get("path") == path and e.get("type") == "file":
217
+ print((e.get("lfs") or {}).get("size") or e.get("size") or 0)
218
+ break
219
+ else:
220
+ print(0)
221
+ except Exception:
222
+ print(0)
223
+ PYEOF
224
+ }
225
+
226
+ fetch() { # repo path-in-repo dest expected-bytes|auto
227
+ local repo="$1" path="$2" dest="$3" want="$4" have base
228
+ base="$(basename "$dest")"
229
+ [ "$want" = "auto" ] && want="$(hf_size "$repo" "$path")"
230
+ mkdir -p "$(dirname "$dest")"
231
+ if [ -f "$dest" ]; then
232
+ have=$(stat -c %s "$dest")
233
+ if [ "$want" = "0" ] || [ "$have" = "$want" ]; then
234
+ ok "cached $base"; REPORT+=("OK $base"); return 0
235
+ fi
236
+ warn "size mismatch ($have != $want) -> refetch $base"
237
+ rm -f "$dest"
238
+ fi
239
+ printf ' downloading %s ...\n' "$base"
240
+ rm -rf "$STAGE"
241
+ if ! "$HF_BIN" download "$repo" "$path" --local-dir "$STAGE" >/dev/null 2>&1; then
242
+ err "download failed: $repo :: $path"; REPORT+=("MISSING $base")
243
+ rm -rf "$STAGE"; return 1
244
+ fi
245
+ mv -f "$STAGE/$path" "$dest"; rm -rf "$STAGE"
246
+ have=$(stat -c %s "$dest")
247
+ if [ "$want" = "0" ] || [ "$have" = "$want" ]; then
248
+ ok "ok $base ($have B)"; REPORT+=("OK $base")
249
+ else
250
+ err "BAD SIZE $base: $have != $want"; REPORT+=("CORRUPT $base")
251
+ return 1
252
+ fi
253
+ }
254
+
255
+ fetch_url() { # url dest expected-bytes
256
+ local url="$1" dest="$2" want="$3" have base
257
+ base="$(basename "$dest")"
258
+ mkdir -p "$(dirname "$dest")"
259
+ if [ -f "$dest" ] && [ "$(stat -c %s "$dest")" = "$want" ]; then
260
+ ok "cached $base"; REPORT+=("OK $base"); return 0
261
+ fi
262
+ rm -f "$dest"
263
+ printf ' downloading %s ...\n' "$base"
264
+ aria2c -x8 -s8 -q --allow-overwrite=true \
265
+ -d "$(dirname "$dest")" -o "$base" "$url" >/dev/null 2>&1 \
266
+ || curl -sL "$url" -o "$dest" \
267
+ || { err "download failed: $url"; REPORT+=("MISSING $base"); return 1; }
268
+ have=$(stat -c %s "$dest")
269
+ if [ "$have" = "$want" ]; then
270
+ ok "ok $base ($have B)"; REPORT+=("OK $base")
271
+ else
272
+ err "BAD SIZE $base: $have != $want"; REPORT+=("CORRUPT $base")
273
+ fi
274
+ }
275
+
276
+ if [ "${SKIP_MODELS:-0}" = "1" ]; then
277
+ log "Models: skipped (SKIP_MODELS=1)"
278
+ else
279
+ if [ "$WANT_FLUX" = "1" ]; then
280
+ log "Models: Flux.2 Klein 9b (~19 GB)"
281
+ fetch "$GATED_REPO" "$GATED_FILE" \
282
+ "$M/diffusion_models/Flux-2-Klein-9b-fp8.safetensors" 9433061528
283
+ fetch Comfy-Org/vae-text-encorder-for-flux-klein-9b \
284
+ split_files/text_encoders/qwen_3_8b_fp8mixed.safetensors \
285
+ "$M/text_encoders/qwen_3_8b_fp8mixed.safetensors" 8664848742
286
+ fetch Comfy-Org/vae-text-encorder-for-flux-klein-9b \
287
+ split_files/vae/flux2-vae.safetensors \
288
+ "$M/vae/flux2-vae.safetensors" 336211292
289
+ fetch bond12321/bfs-head-klein9b \
290
+ bfs_head_v1_flux-klein_9b_step3500_rank128.safetensors \
291
+ "$M/loras/bfs_head_v1_flux-klein_9b_step3500_rank128.safetensors" 662729912
292
+ else
293
+ log "Models: Flux stage skipped"
294
+ REPORT+=("SKIPPED Flux.2 Klein stage")
295
+ fi
296
+
297
+ if [ "$WANT_ZIMAGE" = "1" ]; then
298
+ log "Models: Z-Image Turbo (~21 GB)"
299
+ fetch Comfy-Org/z_image_turbo \
300
+ split_files/diffusion_models/z_image_turbo_bf16.safetensors \
301
+ "$M/diffusion_models/z_image_turbo_bf16.safetensors" 12309866400
302
+ fetch Comfy-Org/z_image_turbo \
303
+ split_files/text_encoders/qwen_3_4b.safetensors \
304
+ "$M/text_encoders/qwen_3_4b.safetensors" 8044982048
305
+ fetch Comfy-Org/z_image_turbo \
306
+ split_files/vae/ae.safetensors \
307
+ "$M/vae/ae.safetensors" 335304388
308
+ else
309
+ log "Models: Z-Image stage skipped"
310
+ REPORT+=("SKIPPED Z-Image stage")
311
+ fi
312
+
313
+ if [ "$WANT_DETECTORS" = "1" ]; then
314
+ log "Models: face detectors"
315
+ fetch Bingsu/adetailer face_yolov8m.pt \
316
+ "$M/ultralytics/bbox/face_yolov8m.pt" 52026019
317
+ fetch_url https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth \
318
+ "$M/sams/sam_vit_b_01ec64.pth" 375042383
319
+ fi
320
+
321
+ # -- user supplied LoRAs -------------------------------------------------
322
+ if [ -n "$EXTRA_LORAS" ]; then
323
+ log "Models: your own LoRAs"
324
+ IFS=',' read -ra ITEMS <<< "$EXTRA_LORAS"
325
+ for item in "${ITEMS[@]}"; do
326
+ item="$(echo "$item" | xargs)"
327
+ [ -z "$item" ] && continue
328
+ lrepo="${item%%:*}"; lpath="${item#*:}"
329
+ if [ "$lrepo" = "$item" ] || [ -z "$lpath" ]; then
330
+ err "bad EXTRA_LORAS entry: '$item' (expected repo:file.safetensors)"
331
+ continue
332
+ fi
333
+ fetch "$lrepo" "$lpath" "$M/loras/$(basename "$lpath")" auto
334
+ done
335
+ fi
336
+ fi
337
+
338
+ # ----------------------------------------------------------- workflows ----
339
+ if [ "$GET_WORKFLOWS" = "1" ]; then
340
+ log "Workflows"
341
+ WFDIR="$COMFY/user/default/workflows"
342
+ mkdir -p "$WFDIR"
343
+ rm -rf "$STAGE"
344
+ if "$HF_BIN" download "$SETUP_REPO" --include "workflows/*.json" \
345
+ --local-dir "$STAGE" >/dev/null 2>&1 && [ -d "$STAGE/workflows" ]; then
346
+ cp -f "$STAGE"/workflows/*.json "$WFDIR"/ 2>/dev/null \
347
+ && ok "installed: $(ls -1 "$WFDIR" | tr '\n' ' ')" \
348
+ || warn "no .json found in $SETUP_REPO/workflows/"
349
+ else
350
+ warn "no workflows/ folder in $SETUP_REPO - load the .json by hand"
351
+ fi
352
+ rm -rf "$STAGE"
353
+ fi
354
+
355
+ # ------------------------------------------------------------- jupyter ----
356
+ if [ "${START_JUPYTER:-0}" = "1" ] && command -v jupyter >/dev/null 2>&1; then
357
+ JT="${JUPYTER_PASSWORD:-$(python -c 'import secrets; print(secrets.token_hex(16))')}"
358
+ nohup jupyter lab --allow-root --no-browser --ip=0.0.0.0 --port=8888 \
359
+ --ServerApp.token="$JT" --ServerApp.allow_origin='*' \
360
+ --notebook-dir="$ROOT" >"$ROOT/jupyter.log" 2>&1 &
361
+ log "JupyterLab :8888 token = $JT"
362
+ fi
363
+
364
+ # ======================================================== final report ====
365
+ ELAPSED=$(( $(date +%s) - START_TS ))
366
+ printf '\n\n'
367
+ printf '\033[1m=========================== SUMMARY ===========================\033[0m\n'
368
+ for line in "${REPORT[@]}"; do
369
+ case "$line" in
370
+ OK*) printf ' \033[1;32m%s\033[0m\n' "$line" ;;
371
+ SKIPPED*) printf ' \033[1;34m%s\033[0m\n' "$line" ;;
372
+ *) printf ' \033[1;31m%s\033[0m\n' "$line" ;;
373
+ esac
374
+ done
375
+ printf '\033[1m=================================================================\033[0m\n'
376
+ printf ' time: %s s\n' "$ELAPSED"
377
+
378
+ if [ "$PROBLEMS" -eq 0 ]; then
379
+ printf '\n\033[1;32m ALL GOOD - ComfyUI is starting on port %s\033[0m\n' "$PORT"
380
+ printf '\033[1;32m Connect -> HTTP Service -> :%s\033[0m\n\n' "$PORT"
381
+ else
382
+ printf '\n\033[1;33m %s PROBLEM(S) ABOVE - ComfyUI still starts, but some\n' "$PROBLEMS"
383
+ printf ' nodes in the workflow will be red / models missing.\n'
384
+ printf ' Full log: %s/provision.log\033[0m\n\n' "$ROOT"
385
+ fi
386
+
387
+ cd "$COMFY"
388
+ exec python main.py --listen 0.0.0.0 --port "$PORT"
workflows/darkHUB-Flux-FaceSwap.json ADDED
The diff for this file is too large to render. See raw diff