Last active
June 13, 2026 01:33
-
-
Save Saik0s/540fa755f4a992039d184eb20291c119 to your computer and use it in GitHub Desktop.
Qwen-Image 2512 ComfyUI provisioning script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Qwen-Image 2512 ComfyUI Provisioning Script | |
| # Downloads Qwen-Image models, LoRAs, writes workflow, installs comfyui-kjnodes. | |
| # | |
| # Required: HF_TOKEN (for HuggingFace), CIVITAI_TOKEN (optional) | |
| # Run as Vast.ai --onstart-cmd or standalone on a ComfyUI instance. | |
| set -euo pipefail | |
| # ── Configuration ─────────────────────────────────────────────── | |
| WORKSPACE_DIR="/workspace" | |
| COMFYUI_DIR="${WORKSPACE_DIR}/ComfyUI" | |
| MODELS_DIR="${COMFYUI_DIR}/models" | |
| WORKFLOW_DIR="${COMFYUI_DIR}/user/default/workflows" | |
| PROVISIONING_LOG="/var/log/portal/provisioning.log" | |
| # HF download parallelism | |
| HF_SEMAPHORE_DIR="${WORKSPACE_DIR}/.qwen_hf_sem" | |
| HF_MAX_PARALLEL=3 | |
| # Check required token | |
| if [ -z "$HF_TOKEN" ]; then | |
| echo "[qwen] ERROR: HF_TOKEN not set" | |
| exit 1 | |
| fi | |
| # ── Directory setup ──────────────────────────────────────────── | |
| mkdir -p "$MODELS_DIR"/{diffusion_models,text_encoders/qwen,vae/qwen-image,loras} | |
| mkdir -p "$WORKFLOW_DIR" | |
| mkdir -p "$(dirname "$PROVISIONING_LOG")" 2>/dev/null || true | |
| rm -rf "$HF_SEMAPHORE_DIR" | |
| mkdir -p "$HF_SEMAPHORE_DIR" | |
| # ── Logging & Cleanup ────────────────────────────────────────── | |
| script_error() { | |
| local line_no=$1 | |
| echo "[qwen] ERROR at line ${line_no} (exit $?)" | tee -a /var/log/portal/comfyui.log | |
| exit 1 | |
| } | |
| trap 'script_error $LINENO' ERR | |
| script_cleanup() { | |
| rm -rf "$HF_SEMAPHORE_DIR" | |
| echo "[qwen] cleanup done $(date -Iseconds)" | tee -a "$PROVISIONING_LOG" | |
| } | |
| trap script_cleanup EXIT | |
| exec > >(tee -a "$PROVISIONING_LOG") 2>&1 | |
| echo "[qwen] provision start $(date -Iseconds)" | |
| # ── Download helpers ─────────────────────────────────────────── | |
| acquire_slot() { | |
| while true; do | |
| local count | |
| count=$(find "$HF_SEMAPHORE_DIR" -name 'slot_*' 2>/dev/null | wc -l | tr -d ' ') | |
| if [ "$count" -lt "$HF_MAX_PARALLEL" ]; then | |
| mktemp -p "$HF_SEMAPHORE_DIR" slot_XXXXXX | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| } | |
| release_slot() { rm -f "$1"; } | |
| download_hf_file() { | |
| local repo="$1" filename="$2" dest="$3" | |
| local dest_dir; dest_dir=$(dirname "$dest") | |
| if [ -f "$dest" ] && [ -s "$dest" ]; then | |
| echo "[qwen] skip (exists): ${dest}" | |
| return 0 | |
| fi | |
| mkdir -p "$dest_dir" | |
| local slot; slot=$(acquire_slot) | |
| local attempt=1 max_retries=5 delay=2 | |
| while [ "$attempt" -le "$max_retries" ]; do | |
| echo "[qwen] HF ${repo}/${filename} (${attempt}/${max_retries})" | |
| if HF_XET_HIGH_PERFORMANCE=1 hf download "$repo" "$filename" \ | |
| --token "$HF_TOKEN" \ | |
| --local-dir "$dest_dir" \ | |
| ; then | |
| local downloaded="${dest_dir}/${filename}" | |
| if [ -f "$downloaded" ] && [ "$downloaded" != "$dest" ]; then | |
| mv "$downloaded" "$dest" | |
| fi | |
| release_slot "$slot" | |
| return 0 | |
| fi | |
| sleep "$delay" | |
| delay=$((delay * 2)) | |
| attempt=$((attempt + 1)) | |
| done | |
| release_slot "$slot" | |
| echo "[qwen] FAILED: ${repo}/${filename}" | |
| return 1 | |
| } | |
| download_civitai_file() { | |
| local version_id="$1" dest="$2" | |
| if [ -f "$dest" ] && [ -s "$dest" ]; then | |
| echo "[qwen] skip (exists): ${dest}" | |
| return 0 | |
| fi | |
| if [ -z "${CIVITAI_TOKEN:-}" ]; then | |
| echo "[qwen] SKIP (no CIVITAI_TOKEN): ${dest}" | |
| return 0 | |
| fi | |
| mkdir -p "$(dirname "$dest")" | |
| local url="https://civitai.com/api/download/models/${version_id}?token=${CIVITAI_TOKEN}" | |
| echo "[qwen] civitai ${version_id} -> ${dest}" | |
| for i in $(seq 1 3); do | |
| if wget -q --tries=3 --retry-connrefused --timeout=120 -O "$dest" "$url"; then | |
| if [ -s "$dest" ]; then return 0; fi | |
| fi | |
| sleep 5 | |
| done | |
| echo "[qwen] FAILED civitai: ${version_id}" | |
| return 1 | |
| } | |
| # ── Model Downloads ──────────────────────────────────────────── | |
| echo "[qwen] downloading models..." | |
| # 1. VAE: qwen_image_vae.safetensors | |
| download_hf_file \ | |
| "Comfy-Org/Qwen-Image_ComfyUI" \ | |
| "split_files/vae/qwen_image_vae.safetensors" \ | |
| "${MODELS_DIR}/vae/qwen-image/qwen_image_vae.safetensors" | |
| # 2. Text Encoder: qwen_2.5_vl_7b_fp8_scaled.safetensors | |
| download_hf_file \ | |
| "Comfy-Org/Qwen-Image_ComfyUI" \ | |
| "split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors" \ | |
| "${MODELS_DIR}/text_encoders/qwen/qwen_2.5_vl_7b_fp8_scaled.safetensors" | |
| # 3. Lightning LoRA (8-step speedup) | |
| download_hf_file \ | |
| "lightx2v/Qwen-Image-2512-Lightning" \ | |
| "Qwen-Image-2512-Lightning-8steps-V1.0-bf16.safetensors" \ | |
| "${MODELS_DIR}/loras/Qwen-Image-2512-Lightning-8steps-V1.0-bf16.safetensors" | |
| echo "[qwen] HF models done" | |
| # ── Civitai Models ───────────────────────────────────────────── | |
| echo "[qwen] downloading Civitai models..." | |
| # === EDIT THESE VERSION IDs BEFORE RUNNING === | |
| # | |
| # Get version IDs: | |
| # curl -s "https://civitai.com/api/v1/models/MODEL_ID" | jq '.modelVersions[0].id' | |
| # | |
| # absoluteRealismV01_qwenV10 — search "AbsoluteReality Qwen" on civitai | |
| # amateurphotography_v1_qwen — civitai model 652699 (Amateur Photography) | |
| # female-body-beauty_qwen — search "female body beauty qwen" on civitai | |
| # samsung_qwen2512 — search "samsung qwen" on civitai | |
| CIVITAI_MODELS=( | |
| # version_id|dest_path | |
| # "CHANGE_ME|${MODELS_DIR}/diffusion_models/absoluteRealismV01_qwenV10.safetensors" | |
| # "CHANGE_ME|${MODELS_DIR}/loras/amateurphotography_v1_qwen.safetensors" | |
| # "CHANGE_ME|${MODELS_DIR}/loras/female-body-beauty_qwen.safetensors" | |
| # "CHANGE_ME|${MODELS_DIR}/loras/samsung_qwen2512.safetensors" | |
| ) | |
| for entry in "${CIVITAI_MODELS[@]}"; do | |
| version_id="${entry%%|*}" | |
| dest="${entry##*|}" | |
| download_civitai_file "$version_id" "$dest" || true | |
| done | |
| # ── Workflow ─────────────────────────────────────────────────── | |
| echo "[qwen] writing workflow JSON..." | |
| cat <<'ENDOFB64' | base64 -d > "${WORKFLOW_DIR}/qwen_Image_2512_8steps_v2_api.json" | |
| ewogICI2MCI6IHsKICAgICJpbnB1dHMiOiB7CiAgICAgICJmaWxlbmFtZV9wcmVmaXgiOiAiUXdlbi1JbWFnZS0yNTEyIiwKICAgICAgImltYWdlcyI6IFsKICAgICAgICAiMjM4OjIzMSIsCiAgICAgICAgMAogICAgICBdCiAgICB9LAogICAgImNsYXNzX3R5cGUiOiAiU2F2ZUltYWdlIiwKICAgICJfbWV0YSI6IHsKICAgICAgInRpdGxlIjogIlNhdmUgSW1hZ2UiCiAgICB9CiAgfSwKICAiNjEiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAibG9yYV9uYW1lIjogIlF3ZW4tSW1hZ2UtMjUxMl9Mb1JhX2J5LUFJX0NoYXJhY3RlcnNfU1RZTEVfU21hcnRwaG9uZVNuYXBzaG90UGhvdG9SZWFsaXR5X3YxMF9UUklHR0VSJGFtdHIgc25hcHNob3QgcGhvdG8kLnNhZmV0ZW5zb3JzIiwKICAgICAgInN0cmVuZ3RoX21vZGVsIjogMC43LAogICAgICAibW9kZWwiOiBbCiAgICAgICAgIjIzODoyMjEiLAogICAgICAgIDAKICAgICAgXQogICAgfSwKICAgICJjbGFzc190eXBlIjogIkxvcmFMb2FkZXJNb2RlbE9ubHkiLAogICAgIl9tZXRhIjogewogICAgICAidGl0bGUiOiAiTG9hZCBMb1JBIgogICAgfQogIH0sCiAgIjYyIjogewogICAgImlucHV0cyI6IHsKICAgICAgImxvcmFfbmFtZSI6ICJhbWF0ZXVycGhvdG9ncmFwaHlfdjFfcXdlbi5zYWZldGVuc29ycyIsCiAgICAgICJzdHJlbmd0aF9tb2RlbCI6IDAuNSwKICAgICAgIm1vZGVsIjogWwogICAgICAgICI2MSIsCiAgICAgICAgMAogICAgICBdCiAgICB9LAogICAgImNsYXNzX3R5cGUiOiAiTG9yYUxvYWRlck1vZGVsT25seSIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJMb2FkIExvUkEiCiAgICB9CiAgfSwKICAiNjMiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAibG9yYV9uYW1lIjogInNhbXN1bmdfcXdlbjI1MTIuc2FmZXRlbnNvcnMiLAogICAgICAic3RyZW5ndGhfbW9kZWwiOiAwLjUsCiAgICAgICJtb2RlbCI6IFsKICAgICAgICAiNjIiLAogICAgICAgIDAKICAgICAgXQogICAgfSwKICAgICJjbGFzc190eXBlIjogIkxvcmFMb2FkZXJNb2RlbE9ubHkiLAogICAgIl9tZXRhIjogewogICAgICAidGl0bGUiOiAiTG9hZCBMb1JBIgogICAgfQogIH0sCiAgIjIzODoyMTkiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAiY2xpcF9uYW1lIjogInF3ZW4vcXdlbl8yLjVfdmxfN2JfZnA4X3NjYWxlZC5zYWZldGVuc29ycyIsCiAgICAgICJ0eXBlIjogInF3ZW5faW1hZ2UiLAogICAgICAiZGV2aWNlIjogImRlZmF1bHQiCiAgICB9LAogICAgImNsYXNzX3R5cGUiOiAiQ0xJUExvYWRlciIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJMb2FkIENMSVAiCiAgICB9CiAgfSwKICAiMjM4OjIyMCI6IHsKICAgICJpbnB1dHMiOiB7CiAgICAgICJ2YWVfbmFtZSI6ICJxd2VuLWltYWdlL3F3ZW5faW1hZ2VfdmFlLnNhZmV0ZW5zb3JzIgogICAgfSwKICAgICJjbGFzc190eXBlIjogIlZBRUxvYWRlciIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJMb2FkIFZBRSIKICAgIH0KICB9LAogICIyMzg6MjI2IjogewogICAgImlucHV0cyI6IHsKICAgICAgInVuZXRfbmFtZSI6ICJhYnNvbHV0ZVJlYWxpc21WMDFfcXdlblYxMC5zYWZldGVuc29ycyIsCiAgICAgICJ3ZWlnaHRfZHR5cGUiOiAiZGVmYXVsdCIKICAgIH0sCiAgICAiY2xhc3NfdHlwZSI6ICJVTkVUTG9hZGVyIiwKICAgICJfbWV0YSI6IHsKICAgICAgInRpdGxlIjogIkxvYWQgRGlmZnVzaW9uIE1vZGVsIgogICAgfQogIH0sCiAgIjIzODoyMzIiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAid2lkdGgiOiA5MjgsCiAgICAgICJoZWlnaHQiOiAxNjY0LAogICAgICAiYmF0Y2hfc2l6ZSI6IDEKICAgIH0sCiAgICAiY2xhc3NfdHlwZSI6ICJFbXB0eVNEM0xhdGVudEltYWdlIiwKICAgICJfbWV0YSI6IHsKICAgICAgInRpdGxlIjogIkVtcHR5U0QzTGF0ZW50SW1hZ2UiCiAgICB9CiAgfSwKICAiMjM4OjIyNyI6IHsKICAgICJpbnB1dHMiOiB7CiAgICAgICJ0ZXh0IjogIkEgcGhvdG9yZWFsaXN0aWMgaW1hZ2Ugb2YgYW4gMTgteWVhci1vbGQgZ2lybCB3aXRoIGRhcmsgY3VybHkgaGFpciwgd2VhcmluZyBhIHB1cnBsZSBib2R5c3VpdCB3aGlsZSBkb2luZyBjb29sLWRvd24gc3RyZXRjaGVzIGFmdGVyIGRhbmNlIGNsYXNzLiBTaGUncyBpbiBhIHNlYXRlZCBmb3J3YXJkIGZvbGQsIGNvbXBsZXRlbHkgZm9jdXNlZCBvbiBoZXIgYnJlYXRoaW5nLCB1bmF3YXJlIG9mIHRoZSBjYW1lcmEgcG9zaXRpb25lZCBiZWhpbmQgdGhlIGRhbmNlIHN0dWRpbyBtaXJyb3JzLiBUaGUgYm9keXN1aXQgaXMgcmlkaW5nIHVwIGludG8gaGVyIGFzcyBhbmQgcHVsbGluZyB0aWdodCBhY3Jvc3MgaGVyIHB1c3N5LCBjcmVhdGluZyBhIGRlZXAgY2FtZWx0b2UgdGhhdCdzIGltcG9zc2libGUgdG8gbWlzcy4gSGVyIGZhY2UgaXMgcmVsYXhlZCBhbmQgcGVhY2VmdWwsIHNsaWdodGx5IGZsdXNoZWQgZnJvbSBkYW5jaW5nLiBUaGUgY2FtZXJhIGNhcHR1cmVzIGhlciBmcm9tIGJlaGluZCBhbmQgc2xpZ2h0bHkgYmVsb3csIGVtcGhhc2l6aW5nIHRoZSBpbnRpbWF0ZSB2aWV3IG9mIGhlciBzdHJldGNoZWQgZmFicmljLiBTdHVkaW8gbGlnaHRpbmcgY3JlYXRlcyBkcmFtYXRpYyBzaGFkb3dzIGFjcm9zcyBoZXIgY3VydmVzLiBUaGUgYmFja2dyb3VuZCBzaG93cyBvdGhlciBkYW5jZXJzIHBhY2tpbmcgdXAgdGhlaXIgdGhpbmdzLiBUaGUgaW1hZ2UgaGFzIHRoYXQgZ3JhaW55LCBzdXJ2ZWlsbGFuY2UtY2FtZXJhIHF1YWxpdHkgd2l0aCBhdXRoZW50aWMgdGltaW5nIGFuZCBmcmFtaW5nLiBTdHlsZTogdm95ZXVyaXN0aWMsIHJlYWxpc3RpYywgNGsgcmVzb2x1dGlvbi5cbiIsCiAgICAgICJjbGlwIjogWwogICAgICAgICIyMzg6MjE5IiwKICAgICAgICAwCiAgICAgIF0KICAgIH0sCiAgICAiY2xhc3NfdHlwZSI6ICJDTElQVGV4dEVuY29kZSIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJDTElQIFRleHQgRW5jb2RlIChQb3NpdGl2ZSBQcm9tcHQpIgogICAgfQogIH0sCiAgIjIzODoyMjgiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAidGV4dCI6ICLkvY7op4bliLDnjoc444CB5L2O55S76LSo44CB6IKi5L2T5YW55b2i44CB5omL5oyH5YW55b2i44CB55S75Y2i6LaF6L+H5aSx5ZKM44CB6Jue6LyR5oSf44CB5Lq66IS45peg57uG6IqC44CB6YCJ5bqm5YWJ5ruR44CB55S75Y2i5YW35pyJQUnmhJ/jgILmnoTlm77mt7fkubHjgILmloflrZfmqKHns7PvvIzmmbTmm7IiLAogICAgICAiY2xpcCI6IFsKICAgICAgICAiMjM4OjIxOSIsCiAgICAgICAgMAogICAgICBdCiAgICB9LAogICAgImNsYXNzX3R5cGUiOiAiQ0xJUFRleHRFbmNvZGUiLAogICAgIl9tZXRhIjogewogICAgICAidGl0bGUiOiAiQ0xJUCBUZXh0IEVuY29kZSAoTmVnYXRpdmUgUHJvbXB0KSIKICAgIH0KICB9LAogICIyMzg6MjIxIjogewogICAgImlucHV0cyI6IHsKICAgICAgImxvcmFfbmFtZSI6ICJRd2VuLUltYWdlLTI1MTItTGlnaHRuaW5nLThzdGVwcy1WMS4wLWJmMTYuc2FmZXRlbnNvcnMiLAogICAgICAic3RyZW5ndGhfbW9kZWwiOiAxLAogICAgICAibW9kZWwiOiBbCiAgICAgICAgIjIzODoyMjYiLAogICAgICAgIDAKICAgICAgXQogICAgfSwKICAgICJjbGFzc190eXBlIjogIkxvcmFMb2FkZXJNb2RlbE9ubHkiLAogICAgIl9tZXRhIjogewogICAgICAidGl0bGUiOiAiTG9hZCBMb1JBIgogICAgfQogIH0sCiAgIjIzODoyMjIiOiB7CiAgICAiaW5wdXRzIjogewogICAgICAic2hpZnQiOiAzLjEsCiAgICAgICJtb2RlbCI6IFsKICAgICAgICAiNjMiLAogICAgICAgIDAKICAgICAgXQogICAgfSwKICAgICJjbGFzc190eXBlIjogIk1vZGVsU2FtcGxpbmdBdXJhRmxvdyIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJNb2RlbFNhbXBsaW5nQXVyYUZsb3ciCiAgICB9CiAgfSwKICAiMjM4OjIzMCI6IHsKICAgICJpbnB1dHMiOiB7CiAgICAgICJzZWVkIjogNTI1Mjg3MjEzMTQ4NDg4LAogICAgICAic3RlcHMiOiA4LAogICAgICAiY2ZnIjogMSwKICAgICAgInNhbXBsZXJfbmFtZSI6ICJyZXNfMnMiLAogICAgICAic2NoZWR1bGVyIjogImJldGE1NyIsCiAgICAgICJkZW5vaXNlIjogMSwKICAgICAgIm1vZGVsIjogWwogICAgICAgICIyMzg6MjIyIiwKICAgICAgICAwCiAgICAgIF0sCiAgICAgICJwb3NpdGl2ZSI6IFsKICAgICAgICAiMjM4OjIyNyIsCiAgICAgICAgMAogICAgICBdLAogICAgICAibmVnYXRpdmUiOiBbCiAgICAgICAgIjIzODoyMjgiLAogICAgICAgIDAKICAgICAgXSwKICAgICAgImxhdGVudF9pbWFnZSI6IFsKICAgICAgICAiMjM4OjIzMiIsCiAgICAgICAgMAogICAgICBdCiAgICB9LAogICAgImNsYXNzX3R5cGUiOiAiS1NhbXBsZXIiLAogICAgIl9tZXRhIjogewogICAgICAidGl0bGUiOiAiS1NhbXBsZXIiCiAgICB9CiAgfSwKICAiMjM4OjIzMSI6IHsKICAgICJpbnB1dHMiOiB7CiAgICAgICJzYW1wbGVzIjogWwogICAgICAgICIyMzg6MjMwIiwKICAgICAgICAwCiAgICAgIF0sCiAgICAgICJ2YWUiOiBbCiAgICAgICAgIjIzODoyMjAiLAogICAgICAgIDAKICAgICAgXQogICAgfSwKICAgICJjbGFzc190eXBlIjogIlZBRURlY29kZSIsCiAgICAiX21ldGEiOiB7CiAgICAgICJ0aXRsZSI6ICJWQUUgRGVjb2RlIgogICAgfQogIH0KfQ== | |
| ENDOFB64 | |
| echo "[qwen] workflow written" | |
| # ── Custom Nodes ─────────────────────────────────────────────── | |
| echo "[qwen] waiting for ComfyUI Manager..." | |
| for i in $(seq 1 120); do | |
| if [ -f "${COMFYUI_DIR}/custom_nodes/ComfyUI-Manager/cm-cli.py" ] && [ -x /venv/main/bin/python3.12 ]; then | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| echo "[qwen] installing comfyui-kjnodes..." | |
| COMFYUI_PATH="$COMFYUI_DIR" /venv/main/bin/python3.12 \ | |
| "${COMFYUI_DIR}/custom_nodes/ComfyUI-Manager/cm-cli.py" install comfyui-kjnodes | |
| echo "[qwen] restarting ComfyUI..." | |
| supervisorctl restart comfyui || true | |
| echo "[qwen] PROVISION COMPLETE $(date -Iseconds)" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment