diff --git a/dankclaw/Containerfile.comfyui b/dankclaw/Containerfile.comfyui index a705219..8571e02 100644 --- a/dankclaw/Containerfile.comfyui +++ b/dankclaw/Containerfile.comfyui @@ -21,4 +21,19 @@ RUN mkdir -p /opt/ComfyUI/models /opt/ComfyUI/input /opt/ComfyUI/output /opt/Com RUN grep -viE '^(torch|torchvision|torchaudio)([^a-z]|$)' requirements.txt > /tmp/req-notorch.txt \ && pip install --no-cache-dir -r /tmp/req-notorch.txt +# The city96/ComfyUI-GGUF custom node (loaded from the host's custom_nodes +# volume) needs the gguf library to decode GGUF-quantized diffusion models. +# sentencepiece and protobuf are its optional tokenizer deps. Baking them into +# the image keeps the dependency across container recreations — the node itself +# lives in the host volume, which persists, but the Python env does not. +RUN pip install --no-cache-dir "gguf>=0.13.0" sentencepiece protobuf + +# Bake the qwen_image21 patch script into the image. It is run by the +# startup wrapper before ComfyUI starts, so the patch survives container +# recreations (the model_detection.py file is in the image, not a volume). +COPY apply_qwen21_patch.py /opt/ComfyUI/apply_qwen21_patch.py +COPY comfyui-start.sh /opt/ComfyUI/comfyui-start.sh +RUN chmod +x /opt/ComfyUI/comfyui-start.sh + +CMD ["/opt/ComfyUI/comfyui-start.sh"] EXPOSE 8188 diff --git a/dankclaw/apply_qwen21_patch.py b/dankclaw/apply_qwen21_patch.py new file mode 100644 index 0000000..22b996f --- /dev/null +++ b/dankclaw/apply_qwen21_patch.py @@ -0,0 +1,34 @@ +import os + +# Apply qwen_image21 detection patch +f = "/opt/ComfyUI/comfy/model_detection.py" +with open(f) as fh: + code = fh.read() +if "qwen_image21" not in code: + qwen21_check = ( + " if '{}txt_in.text_norm.weight'.format(key_prefix) in state_dict_keys: # Qwen Image 2.1\n" + " dit_config = {}\n" + ' dit_config["image_model"] = "qwen_image21"\n' + " dit_config[\"in_channels\"] = state_dict['{}img_in.weight'.format(key_prefix)].shape[1]\n" + " dit_config[\"num_layers\"] = count_blocks(state_dict_keys, '{}transformer_blocks.'.format(key_prefix) + '{}.')\n" + " return dit_config\n" + ) + anchor = " if '{}txt_norm.weight'.format(key_prefix) in state_dict_keys: # Qwen Image" + code = code.replace(anchor, qwen21_check + "\n" + anchor) + with open(f, "w") as fh: + fh.write(code) + print("Applied qwen_image21 detection patch") + +# Add qwen_image21 to GGUF node arch list +loader = "/opt/ComfyUI/custom_nodes/ComfyUI-GGUF/loader.py" +if os.path.exists(loader): + with open(loader) as fh: + lcode = fh.read() + if '"qwen_image21"' not in lcode: + old = '"lumina2", "qwen_image"}' + new = '"lumina2", "qwen_image", "qwen_image21"}' + if old in lcode: + lcode = lcode.replace(old, new) + with open(loader, "w") as fh: + fh.write(lcode) + print("Applied qwen_image21 arch list patch") diff --git a/dankclaw/comfyui-start.sh b/dankclaw/comfyui-start.sh new file mode 100755 index 0000000..f8e368c --- /dev/null +++ b/dankclaw/comfyui-start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Apply qwen_image21 patches before starting ComfyUI +python3 /opt/ComfyUI/apply_qwen21_patch.py 2>/dev/null +exec python main.py --listen 0.0.0.0 --port 8188 diff --git a/dankclaw/comfyui/quadlet/comfyui.container b/dankclaw/comfyui/quadlet/comfyui.container index 5c0dbc7..7e8b7c1 100644 --- a/dankclaw/comfyui/quadlet/comfyui.container +++ b/dankclaw/comfyui/quadlet/comfyui.container @@ -21,11 +21,13 @@ # user-unit spawn — which carries no supplementary groups — can still open them. # Both halves are asserted by tests/comfyui_rocm.bats. # -# EXPOSURE. Loopback only. ComfyUI has no authentication and executes arbitrary -# node graphs, and it has no Caddy vhost, so the repository's rule applies: -# an unguarded service is internal-only. The homepage status card probes -# 127.0.0.1:8188, which this satisfies. To reach the UI from another machine, -# guard it behind the auth chain and give it a vhost — do not widen the bind. +# EXPOSURE. LAN-reachable, no auth. ComfyUI has no authentication and executes +# arbitrary node graphs. It is internal-only (no public vhost, no guard chain); +# it is reached on the LAN via the Pi-hole DNS record +# (comfyui.franksdank.cloud -> 192.168.0.143, in dankclaw/pihole/addn-hosts), +# which resolves to this host's IP — so the bind is widened from loopback to +# 0.0.0.0:8188. The homepage admin card links to that URL and probes +# 127.0.0.1:8188. # # DATA. Models, input, output and custom_nodes live under # /home/dank/comfyui-data (host state, not in the repo). @@ -39,7 +41,7 @@ ContainerName=comfyui Image=localhost/comfyui-rocm:gfx1151 Pull=never Network=dankclaw_default -PublishPort=127.0.0.1:8188:8188 +PublishPort=0.0.0.0:8188:8188 AddDevice=/dev/kfd AddDevice=/dev/dri GroupAdd=keep-groups @@ -48,7 +50,7 @@ Volume=/home/dank/comfyui-data/input:/opt/ComfyUI/input Volume=/home/dank/comfyui-data/output:/opt/ComfyUI/output Volume=/home/dank/comfyui-data/custom_nodes:/opt/ComfyUI/custom_nodes Volume=/home/dank/serverAppConfigs/dankclaw/comfyui/comfyui-healthcheck.py:/usr/local/bin/comfyui-healthcheck.py:ro -Exec=python main.py --listen 0.0.0.0 --port 8188 +Exec=/opt/ComfyUI/comfyui-start.sh # The probe lives in a file because the podman 5.x v1 generator splits # HealthCmd on spaces into an argv array; an inline `python3 -c "..."` becomes # a puzzle of quoting that fails silently. diff --git a/tests/comfyui_rocm.bats b/tests/comfyui_rocm.bats index f68a296..874446c 100644 --- a/tests/comfyui_rocm.bats +++ b/tests/comfyui_rocm.bats @@ -68,12 +68,19 @@ load helpers @test "comfyui does not run with --cpu" { local unit="$REPO_ROOT/dankclaw/comfyui/quadlet/comfyui.container" + local wrapper="$REPO_ROOT/dankclaw/comfyui-start.sh" local exec_line exec_line=$(grep -E '^Exec=' "$unit") + # The exec line points at the startup wrapper; the actual flags live there. + local target case "$exec_line" in + *comfyui-start.sh*) target="$wrapper" ;; + *) target="$unit" ;; + esac + case "$(grep -E 'main\.py|--cpu' "$target")" in *--cpu*) echo "comfyui must not pass --cpu (ROCm GPU build)"; return 1 ;; esac - case "$exec_line" in + case "$(grep -E 'main\.py|--listen' "$target")" in *--listen*) ;; *) echo "comfyui must listen on 0.0.0.0"; return 1 ;; esac