diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 98f1da0ed024f..65d5afca23580 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -6665,6 +6665,12 @@ githubId = 345808; name = "Jakub Okoński"; }; + fazo96 = { + email = "enrico.fasoli1996@gmail.com"; + github = "fazo96"; + githubId = 2546805; + name = "Enrico Fasoli"; + }; fbeffa = { email = "beffa@fbengineering.ch"; github = "fedeinthemix"; @@ -12025,6 +12031,15 @@ githubId = 918448; name = "Anthony Lodi"; }; + logan-barnett = { + email = "logustus+nixpkgs@gmail.com"; + github = "LoganBarnett"; + githubId = 27005; + keys = [{ + fingerprint = "3F29 5F7D 2427 6467 A9B2 3459 41E4 6FB1 ACEA 3EF0"; + }]; + name = "Logan Barnett"; + }; logo = { email = "logo4poop@protonmail.com"; matrix = "@logo4poop:matrix.org"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index b20e98a9f229b..6e8825ca0cf65 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1350,6 +1350,7 @@ ./services/web-apps/calibre-web.nix ./services/web-apps/castopod.nix ./services/web-apps/coder.nix + ./services/web-apps/comfyui.nix ./services/web-apps/changedetection-io.nix ./services/web-apps/chatgpt-retrieval-plugin.nix ./services/web-apps/cloudlog.nix diff --git a/nixos/modules/services/web-apps/comfyui.nix b/nixos/modules/services/web-apps/comfyui.nix new file mode 100644 index 0000000000000..e239d2b7aedda --- /dev/null +++ b/nixos/modules/services/web-apps/comfyui.nix @@ -0,0 +1,405 @@ +{ config, lib, options, pkgs, ...}: + +with lib; + +let + cfg = config.services.comfyui; + defaultUser = "comfyui"; + defaultGroup = defaultUser; + service-name = "comfyui"; + mkComfyUIPackage = cfg: cfg.package.override { + modelsPath = "${cfg.dataPath}/models"; + inputPath = "${cfg.dataPath}/input"; + outputPath = "${cfg.dataPath}/output"; + customNodes = cfg.customNodes; + models = cfg.models; + }; +in +{ + options = { + services.comfyui = { + enable = mkEnableOption + ("The most powerful and modular stable diffusion GUI with a graph/nodes interface."); + + dataPath = mkOption { + type = types.str; + default = "/var/lib/${service-name}"; + description = "Path to the folders which stores models, custom nodes, input and output files."; + }; + + # TODO: This should probably use the global system cudaSupport by default + # and then allow overrides as necessary. + cudaSupport = mkOption { + type = types.bool; + default = false; + description = "Whether or not to enable CUDA for NVidia GPU acceleration."; + defaultText = literalExpression "false"; + example = literalExpression "true"; + }; + + rocmSupport = mkOption { + type = types.bool; + default = false; + description = "Whether or not to enable ROCM for ATi GPU acceleration."; + defaultText = literalExpression "false"; + example = literalExpression "true"; + }; + + package = mkOption { + type = types.package; + default = ( + if config.cudaSupport + then pkgs.comfyui-cuda + else if config.rocmSupport + then pkgs.comfyui-rocm + else pkgs.comfyui-cpu + ); + defaultText = literalExpression "pkgs.comfyui"; + example = literalExpression "pkgs.comfyui-rocm"; + description = "ComfyUI base package to use."; + }; + + user = mkOption { + type = types.str; + default = defaultUser; + example = "yourUser"; + description = '' + The user to run ComfyUI as. + By default, a user named `${defaultUser}` will be created whose home + directory will contain input, output, custom nodes and models. + ''; + }; + + group = mkOption { + type = types.str; + default = defaultGroup; + example = "yourGroup"; + description = '' + The group to run ComfyUI as. + By default, a group named `${defaultUser}` will be created. + ''; + }; + + useCPU = mkOption { + type = types.bool; + default = false; + description = '' + Uses the CPU for everything. Very slow, but needed if there is no hardware acceleration. + ''; + }; + + port = mkOption { + type = types.port; + default = 8188; + description = "Set the listen port for the Web UI and API."; + }; + + customNodes = mkOption { + type = types.listOf types.package; + default = []; + description = "custom nodes to add to the ComfyUI setup. Expects a list of packages from pkgs.comfyui-custom-nodes"; + }; + + listen = mkOption { + type = types.str; + # Assume a higher security posture by default. + default = "127.0.0.1"; + description = "The net mask to listen to."; + example = "0.0.0.0"; + }; + + cors-origin-domain = mkOption { + type = types.str; + default = "disabled"; + description = ''The CORS domain to bless. Use "disabled" to disable. This must include a port''; + example = "foo.com:443"; + }; + + cuda-malloc = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Force cuda-malloc. Leave null to default. + ''; + }; + + max-upload-size = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Max upload size. Leave null to default. + ''; + }; + + multi-user = mkOption { + type = types.bool; + default = false; + description = '' + Enable a simple multi-user mode. This is helpful for separating user + settings but offers nothing in terms of security. + ''; + }; + + cuda-device = mkOption { + type = types.nullOr types.str; + description = ''The CUDA device to use. Query for this using lspci or lshw. Leave as null to auto-detect.''; + default = null; + }; + + verbose = mkOption { + type = types.bool; + description = ''Use verbose logging.''; + default = false; + }; + + cross-attention = mkOption { + type = types.nullOr (types.enum [ + "pytorch" + "quad" + "split" + ]); + description = ''Indicate which cross attention comfyui should use.''; + default = null; + }; + + preview-method = mkOption { + type = types.nullOr (types.enum [ + "none" + "auto" + "latent2rgb" + "taesd" + ]); + description = ''How to preview images being generated.''; + default = null; + }; + + # Argument dump: + # usage: comfyui [-h] [--listen [IP]] [--port PORT] + # [--enable-cors-header [ORIGIN]] + # [--max-upload-size MAX_UPLOAD_SIZE] + # [--extra-model-paths-config PATH [PATH ...]] + # [--output-directory OUTPUT_DIRECTORY] + # [--temp-directory TEMP_DIRECTORY] + # [--input-directory INPUT_DIRECTORY] [--auto-launch] + # [--disable-auto-launch] [--cuda-device DEVICE_ID] + # [--cuda-malloc | --disable-cuda-malloc] + # [--dont-upcast-attention] [--force-fp32 | --force-fp16] + # [--bf16-unet | --fp16-unet | --fp8_e4m3fn-unet | --fp8_e5m2-unet] + # [--fp16-vae | --fp32-vae | --bf16-vae] [--cpu-vae] + # [--fp8_e4m3fn-text-enc | --fp8_e5m2-text-enc | --fp16-text-enc | --fp32-text-enc] + # [--directml [DIRECTML_DEVICE]] [--disable-ipex-optimize] + # [--preview-method [none,auto,latent2rgb,taesd]] + # [--use-split-cross-attention | --use-quad-cross-attention | --use-pytorch-cross-attention] + # [--disable-xformers] + # [--gpu-only | --highvram | --normalvram | --lowvram | --novram | --cpu] + # [--disable-smart-memory] [--deterministic] + # [--dont-print-server] [--quick-test-for-ci] + # [--windows-standalone-build] [--disable-metadata] + # [--multi-user] [--verbose] + extraArgs = mkOption { + type = types.attrsOf types.str; + default = {}; + defaultText = literalExpression '' + { + disable-xformers = true; + preview-method = "auto"; + verbose = true; + } + ''; + description = '' + Additional arguments to be passed to comfyui. + ''; + }; + + models = mkOption (let + src-option = mkOption { + type = types.attrsOf types.package; + default = {}; + description = '' + A key value pair of names to fetchers for ComfyUI models. + ''; + }; + in { + type = types.submodule { + options = { + checkpoints = src-option; + clip = src-option; + clip_vision = src-option; + configs = src-option; + controlnet = src-option; + embeddings = src-option; + loras = src-option; + upscale_models = src-option; + vae = src-option; + vae_approx = src-option; + }; + }; + default = { + checkpoints = {}; + clip = {}; + clip_vision = {}; + configs = {}; + controlnet = {}; + embeddings = {}; + loras = {}; + upscale_models = {}; + vae = {}; + vae_approx = {}; + }; + description = '' + Models for ComfyUI to use. These are categorized by the model type + (checkpoints, loras, vae, etc.). Each of those are an attrset of + names of the model (like `juggernaught-xl`) and a fetcher indicating + the URL, type, and SHA of the model. + ''; + }); + }; + }; + + config = mkIf cfg.enable { + users.users = mkIf (cfg.user == defaultUser) { + ${defaultUser} = + { group = cfg.group; + home = cfg.dataPath; + createHome = true; + description = "ComfyUI daemon user"; + isSystemUser = true; + }; + }; + + users.groups = mkIf (cfg.group == defaultGroup) { + ${defaultGroup} = {}; + }; + + systemd.services.comfyui = let + package = mkComfyUIPackage cfg; + in { + description = "ComfyUI Service"; + wantedBy = [ "multi-user.target" ]; + environment = { + DATA = cfg.dataPath; + }; + + preStart = let + inherit (lib.trivial) throwIfNot; + inherit (lib) isAttr isString; + inherit (lib.strings) concatStrings intersperse; + inherit (lib.lists) flatten; + inherit (lib.attrsets) attrValues mapAttrsToList; + join = (sep: (xs: concatStrings (intersperse sep xs))); + join-lines = join "\n"; + src-to-symlink = path: name: ( + throwIfNot (isString path) "path must be a string." + throwIfNot (isString name) "name must be a string." + lib.traceVal '' + ln -snf ${path}/${name} $out + '' + ); + + # Take all of the model files for the various model types defined in the + # config of `models`, and translate it into a series of symlink shell + # invocations. The destination corresponds to the definitions in + # `config-data`. + # ex: + # + # linkModels + # "/var/lib/comfyui/models" + # { + # checkpoints = { + # "sdxxl.safetensors" = pkgs.fetchurl { + # url = "foo.com/sdxxl-v123"; + # sha256 = "sha-string"; + # }; + # }; + # upscale_modules = { + # "controlnet.pth" = { + # fancy-pth = pkgs.fetchurl { + # url = "foo.com/fancy-pth-v456"; + # sha256 = "sha-string"; + # }; + # }; + # }; + # } + # Returns: '' + # ln -snf /var/lib/comfyui/models/checkpoints + # ln -snf /var/lib/comfyui/models/upscale_modules + # '' + # + linkModels = base-path: models: + throwIfNot (isString base-path) "base-path must be a string." + throwIfNot (isAttrs models) "models must be an attrset." + (join-lines (builtins.map + (x: '' + ln -snf ${x.drv} ${cfg.dataPath}/models/${x.model-type} + '') + (flatten + (mapAttrsToList + (type: models-by-name: { + model-type = type; + drv = pkgs.linkFarm "comfyui-models-${type}" ( + mapAttrsToList (name: path: { + inherit name path; + }) models-by-name + ); + }) + models + ) + ) + )) + ; + in '' + mkdir -p ${cfg.dataPath}/input + mkdir -p ${cfg.dataPath}/output + ln -snf ${package}/custom_nodes ${cfg.dataPath}/custom_nodes + ln -snf ${package}/extra_model_paths.yaml ${cfg.dataPath}/extra_model_paths.yaml + mkdir -p ${cfg.dataPath}/models + ${linkModels "${cfg.dataPath}/models" cfg.models} + ''; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + # These directories must be relative to /var/lib. Absolute paths are + # will error with: + # : StateDirectory= path is absolute, ignoring: + RuntimeDirectory = [ service-name ]; + StateDirectory = [ service-name ]; + WorkingDirectory = "/run/${service-name}"; + ExecStart = let + args = cli.toGNUCommandLine {} ({ + cpu = cfg.useCPU; + enable-cors-header = cfg.cors-origin-domain; + cuda-device = cfg.cuda-device; + listen = cfg.listen; + max-upload-size = cfg.max-upload-size; + multi-user = cfg.multi-user; + port = cfg.port; + verbose = cfg.verbose; + } + // ( + if cfg.cuda-malloc != null + then ( + if cfg.cuda-malloc + then { cuda-malloc = true; } + else { disable-cuda-malloc = true; } + ) + else {} + ) + // (lib.optionalAttrs (cfg.cross-attention != null) { + "use-${cfg.cross-attention}-cross-attention" = true; + }) + // cfg.extraArgs); + in ''${package}/bin/comfyui ${toString args}''; + # comfyui is prone to crashing on long slow workloads. + Restart = "always"; + StartLimitBurst = 3; + }; + unitConfig = { + # Prevent it from restarting _too_ much though. Stop if three times a + # minute. This might need a better default from someone with better + # sysadmin chops. + StartLimitIntervalSec = "1m"; + }; + }; + }; +} diff --git a/pkgs/by-name/co/comfyui/comfyui-custom-scripts-remove-js-install-step.patch b/pkgs/by-name/co/comfyui/comfyui-custom-scripts-remove-js-install-step.patch new file mode 100644 index 0000000000000..805a07cb1f267 --- /dev/null +++ b/pkgs/by-name/co/comfyui/comfyui-custom-scripts-remove-js-install-step.patch @@ -0,0 +1,70 @@ +diff --git a/__init__.py b/__init__.py +index ae43dd2..07435f7 100644 +--- a/__init__.py ++++ b/__init__.py +@@ -21,5 +21,5 @@ if init(): + if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None: + NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS) + +-WEB_DIRECTORY = "./web" ++WEB_DIRECTORY = "./web/js" + __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] +diff --git a/pysssss.py b/pysssss.py +index b1024f5..4f3b122 100644 +--- a/pysssss.py ++++ b/pysssss.py +@@ -117,46 +117,6 @@ def is_junction(path): + except OSError: + return False + +- +-def install_js(): +- src_dir = get_ext_dir("web/js") +- if not os.path.exists(src_dir): +- log("No JS") +- return +- +- should_install = should_install_js() +- if should_install: +- log("it looks like you're running an old version of ComfyUI that requires manual setup of web files, it is recommended you update your installation.", "warning", True) +- dst_dir = get_web_ext_dir() +- linked = os.path.islink(dst_dir) or is_junction(dst_dir) +- if linked or os.path.exists(dst_dir): +- if linked: +- if should_install: +- log("JS already linked") +- else: +- os.unlink(dst_dir) +- log("JS unlinked, PromptServer will serve extension") +- elif not should_install: +- shutil.rmtree(dst_dir) +- log("JS deleted, PromptServer will serve extension") +- return +- +- if not should_install: +- log("JS skipped, PromptServer will serve extension") +- return +- +- if link_js(src_dir, dst_dir): +- log("JS linked") +- return +- +- log("Copying JS files") +- shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True) +- +- +-def should_install_js(): +- return not hasattr(PromptServer.instance, "supports") or "custom_nodes_from_web" not in PromptServer.instance.supports +- +- + def init(check_imports=None): + log("Init") + +@@ -169,7 +129,6 @@ def init(check_imports=None): + type="ERROR", always=True) + return False + +- install_js() + return True + + diff --git a/pkgs/by-name/co/comfyui/custom-nodes.nix b/pkgs/by-name/co/comfyui/custom-nodes.nix new file mode 100644 index 0000000000000..b46139b2742b0 --- /dev/null +++ b/pkgs/by-name/co/comfyui/custom-nodes.nix @@ -0,0 +1,325 @@ +# TODO: Document how to override this data (let alone that it exists). Show how +# to specify the workflow directory ala: +# https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/pysssss.example.json +{ comfyui-custom-scripts-autocomplete-text ? (builtins.fetchurl { + url = "https://gist.githubusercontent.com/pythongosssss/1d3efa6050356a08cea975183088159a/raw/a18fb2f94f9156cf4476b0c24a09544d6c0baec6/danbooru-tags.txt"; + sha256 = "15xmm538v0mjshncglpbkw2xdl4cs6y0faz94vfba70qq87plz4p"; +}) +, comfyui-custom-scripts-data ? { + name = "CustomScripts"; + logging = false; +} +, fetchFromGitHub +, lib +, writeTextFile +, pkgs +, stdenv +}: + +let + # Patches don't apply to $src, and as with many scripting languages that don't + # have a build output per se, we just want the script source itself placed + # into $out. So just copy everything into $out instead of from $src so we can + # make sure we get everything in the future, and we use the patched versions. + install = '' + shopt -s dotglob + shopt -s extglob + cp -r ./!($out|$src) $out/ +''; + mkComfyUICustomNodes = args: stdenv.mkDerivation ({ + installPhase = '' + runHook preInstall + mkdir -p $out/ + ${install} + runHook postInstall + ''; + + passthru.dependencies = []; + } // args); +in +{ + inherit mkComfyUICustomNodes; + + # Generates masks for inpainting based on text prompts.. + # https://github.com/biegert/ComfyUI-CLIPSeg + clipseg = mkComfyUICustomNodes { + pname = "clipseg"; + version = "unstable-2023-04-12"; + pyproject = true; + installPhase = '' + runHook preInstall + mkdir -p $out + cp $src/custom_nodes/clipseg.py $out/__init__.py # https://github.com/biegert/ComfyUI-CLIPSeg/issues/12 + runHook postInstall + ''; + src = fetchFromGitHub { + owner = "biegert"; + repo = "ComfyUI-CLIPSeg"; + rev = "7f38951269888407de45fb934958c30c27704fdb"; + hash = "sha256-qqrl1u1wOKMRRBvMHD9gE80reDqLWn+vJEiM1yKZeUo="; + fetchSubmodules = true; + }; + }; + + # https://github.com/Fannovel16/comfyui_controlnet_aux + # Nodes for providing ControlNet hint images. + controlnet-aux = mkComfyUICustomNodes { + pname = "comfyui-controlnet-aux"; + version = "unstable-2024-04-05"; + pyproject = true; + dependencies = with pkgs.python3Packages; [ + matplotlib + opencv4 + scikit-image + ]; + src = fetchFromGitHub { + owner = "Fannovel16"; + repo = "comfyui_controlnet_aux"; + rev = "c0b33402d9cfdc01c4e0984c26e5aadfae948e05"; + hash = "sha256-D9nzyE+lr6EJ+9Egabu+th++g9ZR05wTg0KSRUBaAZE="; + fetchSubmodules = true; + }; + }; + + # Manages workflows in comfyui such that they can be version controlled + # easily. + # https://github.com/talesofai/comfyui-browser + # + # This uses a fork that allows for configurable directories. + comfyui-browser = mkComfyUICustomNodes { + pname = "comfyui-browser"; + version = "unstable-fork-2024-04-21"; + src = fetchFromGitHub { + owner = "LoganBarnett"; + repo = "comfyui-browser"; + rev = "209106316655a58b7f49695c0a0bcab57d5a0c0e"; + hash = "sha256-/vYCxzT0YvBiNl3B0s8na5QRYWxUgNUleRgCQrEJgvI="; + }; + installPhase = '' + mkdir -p $out/ + ${install} + cp ${pkgs.writeText "config.json" (builtins.toJSON { + collections = "/var/lib/comfyui/comfyui-browser-collections"; + download_logs = "/var/lib/comfyui/comfyui-browser-download-logs"; + outputs = "/var/lib/comfyui/output"; + sources = "/var/lib/comfyui/comfyui-browser-sources"; + })} $out/config.json + ''; + }; + + # Show the time spent in various nodes of a workflow. + comfyui-profiler = mkComfyUICustomNodes { + pname = "comfyui-profiler"; + version = "unstable-2024-01-11"; + src = fetchFromGitHub { + owner = "tzwm"; + repo = "comfyui-profiler"; + rev = "942dfe484c481f7cdab8806fa278b3df371711bf"; + hash = "sha256-J0iTRycneGYF6RGJyZ/mVtEge1dxakwny0yfG1ceOd8="; + }; + }; + + # https://github.com/crystian/ComfyUI-Crystools + # Various tools/nodes: + # 1. Resources monitor (CUDA GPU usage, CPU usage, memory, etc). + # a. CUDA only. + # 2. Progress monitor. + # 3. Compare images. + # 4. Compare workflow JSON documents. + # 5. Debug values. + # 6. Pipes - A means of condensing multiple inputs or outputs together into a + # single output or input (respectively). + # 7. Better nodes for: + # a. Saving images. + # b. Loading images. + # c. See "hidden" data(?). + # 8. New primitives (list), and possibly better/different replacements for + # original primitives. + # 9. Switch - turn on or off functionality based on a boolean primitive. + # 9. More™! + # + comfyui-crystools = mkComfyUICustomNodes (let + version = "1.12.0"; + in { + pname = "comfyui-cystools"; + inherit version; + src = fetchFromGitHub { + owner = "crystian"; + repo = "ComfyUI-Crystools"; + rev = version; + hash = "sha256-ZzbMgFeV5rrRU76r/wKnbhujoqE7UDOSaLgQZhguXuY="; + }; + passthru.dependencies = with pkgs.python3Packages; [ + deepdiff + py-cpuinfo + pynvml + ]; + }); + + # https://github.com/pythongosssss/ComfyUI-Custom-Scripts + # Various tools/nodes: + # 1. Autocomplete of keywords, showing keyword count in the model. + # 2. Auto-arrange graph. + # 3. Always snap to grid. + # 4. Loaders that show preview images, have example prompts, and are cataloged + # under folders. + # 5. Checkpoint/LoRA metadata viewing. + # 6. Image constraints (I assume for preview). + # 7. Favicon for comfyui. + # 8. Image feed showing images of the current session. + # 9. Advanced KSampler denoise "helper" - asks for steps? + # 10. Lock nodes and groups (groups doesn't have this in stock comfyui) to + # prevent moving. + # 11. Math/eval expressions as a node. + # 12. Node finder. + # 13. Preset text - save and reuse text. + # 14. Play sound node - great for notification of completion! + # 15. Repeaters. + # 16. Show text (can be good for loading images and getting the prompt text + # out). + # 17. Show image on menu. + # 18. String (replace) function - Substitution via regular expression or exact + # match. + # 19. Save and load workflows (already in stock?). + # 20. 90º reroutes...? + # 21. Link render mode - linear, spline, straight. + # + comfyui-custom-scripts = mkComfyUICustomNodes (let + pysssss-config = (writeTextFile { + name = "pysssss.json"; + text = (lib.generators.toJSON {} comfyui-custom-scripts-data); + }); + in { + pname = "comfyui-custom-scripts"; + version = "unstable-2024-04-07"; + src = fetchFromGitHub { + owner = "pythongosssss"; + repo = "ComfyUI-Custom-Scripts"; + rev = "3f2c021e50be2fed3c9d1552ee8dcaae06ad1fe5"; + hash = "sha256-Kc0zqPyZESdIQ+umepLkQ/GyUq6fev0c/Z7yyTew5dY="; + }; + installPhase = '' + runHook preInstall + mkdir -p $out/ + cp -r $src/* $out/ + cp ${pysssss-config} $out/pysssss.json + mkdir -p $out/user + chmod +w $out/user + cp ${comfyui-custom-scripts-autocomplete-text} $out/user/autocomplete.txt + chmod -w $out/user + # Copy the patched version separately. See + # https://discourse.nixos.org/t/solved-how-to-apply-a-patch-in-a-flake/27227/4 + # for reference. Perhaps a better reference exists? + # But this doesn't work for reasons I can't understand. I get permission + # denied. + # cp pysssss.py $out/ + # It seems that I need to grant myself write permissions first. Is any of + # this documented anywhere? + chmod -R +w $out + cp pysssss.py $out/ + cp __init__.py $out/ + # Put it back I guess? + chmod -R -w $out/ + runHook postInstall + ''; + patches = [ + ./comfyui-custom-scripts-remove-js-install-step.patch + ]; + }); + + # https://github.com/LEv145/images-grid-comfy-plugin + images-grid-comfy-plugin = mkComfyUICustomNodes (let + version = "2.6"; + in { + pname = "images-grid-comfy-plugin"; + inherit version; + src = fetchFromGitHub { + owner = "LEv145"; + repo = "images-grid-comfy-plugin"; + # Space character is deliberate. + rev = "refs/tags/${version}"; + hash = "sha256-YG08pF6Z44y/gcS9MrCD/X6KqG99ig+VKLfZOd49w9s="; + }; + }); + + # https://github.com/Acly/comfyui-inpaint-nodes + # Provides nodes for doing better inpainting. + inpaint-nodes = mkComfyUICustomNodes { + pname = "comfyui-inpaint-nodes"; + version = "unstable-2024-04-08"; + pyproject = true; + src = fetchFromGitHub { + owner = "Acly"; + repo = "comfyui-inpaint-nodes"; + rev = "8469f5531116475abb6d7e9c04720d0a29485a66"; + hash = "sha256-Ane8zA9BN9QlRcQOwji4hZF2xoDPe/GvSqEyAPR+T28="; + fetchSubmodules = true; + }; + }; + + # https://github.com/cubiq/ComfyUI_IPAdapter_plus + # This allows use of IP-Adapter models (IP meaning Image Prompt in this + # context). IP-Adapter models can out-perform fine tuned models + # (checkpoints?). + ipadapter-plus = mkComfyUICustomNodes { + pname = "comfyui-ipadapter-plus"; + version = "unstable-2024-04-10"; + pyproject = true; + src = fetchFromGitHub { + owner = "cubiq"; + repo = "ComfyUI_IPAdapter_plus"; + rev = "417d806e7a2153c98613e86407c1941b2b348e88"; + hash = "sha256-yuZWc2PsgMRCFSLTqniZDqZxevNt2/na7agKm7Xhy7Y="; + fetchSubmodules = true; + }; + }; + + # https://github.com/Gourieff/comfyui-reactor-node + # Fast and simple face swap node(s). + reactor-node = mkComfyUICustomNodes { + pname = "comfyui-reactor-node"; + version = "unstable-2024-04-07"; + pyproject = true; + dependencies = with pkgs.python3Packages; [ insightface ]; + src = fetchFromGitHub { + owner = "Gourieff"; + repo = "comfyui-reactor-node"; + rev = "05bf228e623c8d7aa5a33d3a6f3103a990cfe09d"; + hash = "sha256-2IrpOp7N2GR1zA4jgMewAp3PwTLLZa1r8D+/uxI8yzw="; + fetchSubmodules = true; + }; + }; + + # https://github.com/Acly/comfyui-tooling-nodes + # Make ComfyUI more friendly towards API usage. + tooling-nodes = mkComfyUICustomNodes { + pname = "comfyui-tooling-nodes"; + version = "unstable-2024-03-04"; + pyproject = true; + src = fetchFromGitHub { + owner = "Acly"; + repo = "comfyui-tooling-nodes"; + rev = "bcb591c7b998e13f12e2d47ee08cf8af8f791e50"; + hash = "sha256-dXeDABzu0bhMDN/ryHac78oTyEBCmM/rxCIPfr99ol0="; + fetchSubmodules = true; + }; + }; + + # Handle upscaling of smaller images into larger ones. This is helpful to go + # from a prototyped image to a highly detailed, high resolution version. + ultimate-sd-upscale = mkComfyUICustomNodes { + pname = "ultimate-sd-upscale"; + version = "unstable-2024-03-30"; + src = fetchFromGitHub { + owner = "ssitu"; + repo = "ComfyUI_UltimateSDUpscale"; + rev = "b303386bd363df16ad6706a13b3b47a1c2a1ea49"; + hash = "sha256-kcvhafXzwZ817y+8LKzOkGR3Y3QBB7Nupefya6s/HF4="; + fetchSubmodules = true; + }; + }; + + # More to add: + # https://github.com/pythongosssss/ComfyUI-WD14-Tagger - Reverse image + # inference - generate keywords (or a prompt of sorts) from an image. +} diff --git a/pkgs/by-name/co/comfyui/krita-ai-custom-nodes.nix b/pkgs/by-name/co/comfyui/krita-ai-custom-nodes.nix new file mode 100644 index 0000000000000..18b8b23b101f5 --- /dev/null +++ b/pkgs/by-name/co/comfyui/krita-ai-custom-nodes.nix @@ -0,0 +1,13 @@ +## +# Krita.ai can use ComfyUI as a backend, but there are some required +# custom-nodes that must be configured beforehand. See +# https://github.com/Acly/krita-ai-diffusion/wiki/ComfyUI-Setup for Krita's +# documentation on the topic. +{ custom-nodes }: with custom-nodes; [ + controlnet-aux + inpaint-nodes + ipadapter-plus + reactor-node + tooling-nodes + ultimate-sd-upscale +] diff --git a/pkgs/by-name/co/comfyui/package.nix b/pkgs/by-name/co/comfyui/package.nix new file mode 100644 index 0000000000000..7ec423c673e86 --- /dev/null +++ b/pkgs/by-name/co/comfyui/package.nix @@ -0,0 +1,148 @@ +{ lib +, python311 +, linkFarm +, writers +, writeTextFile +, fetchFromGitHub +, stdenv +, symlinkJoin +, config +, gpuBackend ? ( + if config.cudaSupport + then "cuda" + else if config.rocmSupport + then "rocm" + else "none" +) +, modelsPath ? "/var/lib/comfyui/models" +, inputPath ? "/var/lib/comfyui/input" +, outputPath ? "/var/lib/comfyui/output" +, tempPath ? "/var/lib/comfyui/temp" +, userPath ? "/var/lib/comfyui/user" +, customNodes ? [] +, models ? { + checkpoints = {}; + clip = {}; + clip_vision = {}; + configs = {}; + controlnet = {}; + embeddings = {}; + upscale_modules = {}; + vae = {}; + vae_approx = {}; + } +}: + +let + + config-data = { + comfyui = { + base_path = modelsPath; + checkpoints = "${modelsPath}/checkpoints"; + clip = "${modelsPath}/clip"; + clip_vision = "${modelsPath}/clip_vision"; + configs = "${modelsPath}/configs"; + controlnet = "${modelsPath}/controlnet"; + embeddings = "${modelsPath}/embeddings"; + loras = "${modelsPath}/loras"; + upscale_models= "${modelsPath}/upscale_models"; + vae = "${modelsPath}/vae"; + vae_approx = "${modelsPath}/vae_approx"; + }; + }; + + modelPathsFile = writeTextFile { + name = "extra_model_paths.yaml"; + text = (lib.generators.toYAML {} config-data); + }; + + pythonEnv = (python311.withPackages (ps: with ps; [ + ( + if gpuBackend == "cuda" + then torchWithCuda + else if gpuBackend == "rocm" + then torchWithRocm + else torch + ) + torchsde + torchvision + torchaudio + transformers + safetensors + accelerate + aiohttp + einops + kornia + pyyaml + pillow + scipy + psutil + tqdm + ] ++ (builtins.concatMap (node: node.dependencies) customNodes))); + + executable = writers.writeDashBin "comfyui" '' + cd $out && \ + ${pythonEnv}/bin/python comfyui \ + --input-directory ${inputPath} \ + --output-directory ${outputPath} \ + --extra-model-paths-config ${modelPathsFile} \ + --temp-directory ${tempPath} \ + "$@" + ''; + + customNodesCollection = ( + linkFarm "comfyui-custom-nodes" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) customNodes) + ); +in stdenv.mkDerivation rec { + pname = "comfyui"; + version = "unstable-2024-04-15"; + + src = fetchFromGitHub { + owner = "comfyanonymous"; + repo = "ComfyUI"; + rev = "45ec1cbe963055798765645c4f727122a7d3e35e"; + hash = "sha256-oK+PwAJdvItK1NaRRJMNI4Oh/g4jNt1M5gWfXEy3C9g="; + }; + + installPhase = '' + runHook preInstall + echo "Preparing bin folder" + mkdir -p $out/bin/ + echo "Copying comfyui files" + # These copies everything over but test/ci/github directories. But it's not + # very future-proof. This can lead to errors such as "ModuleNotFoundError: + # No module named 'app'" when new directories get added (which has happened + # at least once). Investigate if we can just copy everything. + cp -r $src/comfy $out/ + cp -r $src/comfy_extras $out/ + cp -r $src/app $out/ + cp -r $src/web $out/ + cp -r $src/*.py $out/ + mv $out/main.py $out/comfyui + echo "Copying ${modelPathsFile} to $out" + cp ${modelPathsFile} $out/extra_model_paths.yaml + echo "Setting up input and output folders" + ln -s ${inputPath} $out/input + ln -s ${outputPath} $out/output + mkdir -p $out/${tempPath} + echo "Setting up custom nodes" + ln -snf ${customNodesCollection} $out/custom_nodes + echo "Copying executable script" + cp ${executable}/bin/comfyui $out/bin/comfyui + substituteInPlace $out/bin/comfyui --replace "\$out" "$out" + echo "Patching python code..." + substituteInPlace $out/folder_paths.py --replace 'os.path.join(os.path.dirname(os.path.realpath(__file__)), "temp")' '"${tempPath}"' + substituteInPlace $out/folder_paths.py --replace 'os.path.join(os.path.dirname(os.path.realpath(__file__)), "user")' '"${userPath}"' + runHook postInstall + ''; + + # outputs = ["" "extra_model_paths.yaml" "inputs" "outputs"]; + + meta = with lib; { + homepage = "https://github.com/comfyanonymous/ComfyUI"; + description = "The most powerful and modular stable diffusion GUI with a graph/nodes interface."; + license = licenses.gpl3; + platforms = platforms.all; + maintainers = with maintainers; [ fazo96 ]; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index a74d001641803..03d0b241d6cce 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -6703,6 +6703,14 @@ with pkgs; colormake = callPackage ../development/tools/build-managers/colormake { }; + comfyui-cpu = callPackage ../by-name/co/comfyui/package.nix { gpuBackend = "none"; }; + + comfyui-cuda = callPackage ../by-name/co/comfyui/package.nix { gpuBackend = "cuda"; }; + + comfyui-rocm = callPackage ../by-name/co/comfyui/package.nix { gpuBackend = "rocm"; }; + + comfyui-custom-nodes = recurseIntoAttrs (callPackage ../by-name/co/comfyui/custom-nodes.nix { }); + cpuminer = callPackage ../tools/misc/cpuminer { }; crabz = callPackage ../tools/compression/crabz { };