feat: opt-out switch for prestartup pip install/upgrade

Add a `dependency_management` config key (and matching
`COMFYUI_MANAGER_DEPENDENCY_MANAGEMENT` env override) so installs on
externally-managed Python environments — Nix, Guix, system packages,
locked-down corporate images — can disable every prestartup path that
mutates the interpreter.

When set to `off`:
- `PIPFixer.fix_broken()` returns immediately (no torch/opencv/comfy
  rollback, no `comfyui-frontend-package` reinstall).
- The unified dependency resolver is skipped before it tries to
  `uv pip install` against a read-only store.
- Per-node `pip install -r requirements.txt` in
  `execute_lazy_install_script` is skipped.

Read-only operations (`pip list`, `pip freeze`, `pip show`) still run,
so the UI, inventory, and security checks keep functioning. Default is
`on`, preserving today's behavior for every existing user.
This commit is contained in:
Can H. Tartanoglu 2026-05-04 15:19:08 +02:00
parent 01799f8cac
commit 1584887823
No known key found for this signature in database
GPG Key ID: 4623DEA06FDACFE1
2 changed files with 46 additions and 1 deletions

View File

@ -28,6 +28,17 @@ use_uv = False
use_unified_resolver = False use_unified_resolver = False
bypass_ssl = False bypass_ssl = False
# When False, ComfyUI-Manager will not attempt to install, upgrade, or
# uninstall Python packages on its own (prestartup auto-healing, the unified
# dependency resolver, and per-node `pip install -r requirements.txt`). This
# is intended for distro-packaged or otherwise externally-managed Python
# environments (e.g. Nix, Guix, system package managers, locked-down
# corporate images) where the interpreter is read-only.
#
# Read-only pip operations (`list`, `freeze`, `show`) still run so the UI,
# inventory, and security checks keep working.
dependency_management_enabled = True
def is_manager_pip_package(): def is_manager_pip_package():
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes')) return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
@ -412,6 +423,10 @@ class PIPFixer:
self.manager_files_path = manager_files_path self.manager_files_path = manager_files_path
def fix_broken(self): def fix_broken(self):
if not dependency_management_enabled:
logging.info("[ComfyUI-Manager] dependency management disabled; skipping PIPFixer.fix_broken()")
return
new_pip_versions = get_installed_packages(True) new_pip_versions = get_installed_packages(True)
# remove `comfy` python package # remove `comfy` python package

View File

@ -93,6 +93,27 @@ def read_unified_resolver_mode():
if 'use_unified_resolver' in default_conf: if 'use_unified_resolver' in default_conf:
manager_util.use_unified_resolver = default_conf['use_unified_resolver'].lower() == 'true' manager_util.use_unified_resolver = default_conf['use_unified_resolver'].lower() == 'true'
def read_dependency_management_mode():
# Config: [default] dependency_management = on|off (default on).
# Env override: COMFYUI_MANAGER_DEPENDENCY_MANAGEMENT=on|off wins over config.
# `off` opts out of every Python-package mutation Manager performs at
# startup (PIPFixer auto-heal, unified resolver, per-node pip install).
# Intended for distro-packaged / externally-managed Python environments
# where the interpreter is read-only (Nix, Guix, system packages, etc.).
def _is_off(value):
return value.strip().lower() in ('off', 'false', '0', 'no', 'disabled')
env_value = os.environ.get('COMFYUI_MANAGER_DEPENDENCY_MANAGEMENT')
if env_value is not None:
manager_util.dependency_management_enabled = not _is_off(env_value)
elif 'dependency_management' in default_conf:
manager_util.dependency_management_enabled = not _is_off(default_conf['dependency_management'])
if not manager_util.dependency_management_enabled:
logging.info("[ComfyUI-Manager] dependency management disabled; auto-install/upgrade/uninstall paths will be skipped.")
def check_file_logging(): def check_file_logging():
global enable_file_logging global enable_file_logging
if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false': if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false':
@ -102,6 +123,7 @@ def check_file_logging():
read_config() read_config()
read_uv_mode() read_uv_mode()
read_unified_resolver_mode() read_unified_resolver_mode()
read_dependency_management_mode()
security_check.security_check() security_check.security_check()
check_file_logging() check_file_logging()
@ -591,7 +613,11 @@ def execute_lazy_install_script(repo_path, executable):
install_script_path = os.path.join(repo_path, "install.py") install_script_path = os.path.join(repo_path, "install.py")
requirements_path = os.path.join(repo_path, "requirements.txt") requirements_path = os.path.join(repo_path, "requirements.txt")
if os.path.exists(requirements_path) and not _unified_resolver_succeeded: if (
os.path.exists(requirements_path)
and not _unified_resolver_succeeded
and manager_util.dependency_management_enabled
):
# Per-node pip install: only runs if unified resolver is disabled or failed # Per-node pip install: only runs if unified resolver is disabled or failed
print(f"Install: pip packages for '{repo_path}'") print(f"Install: pip packages for '{repo_path}'")
@ -764,6 +790,10 @@ def execute_startup_script():
# --- Unified dependency resolver: batch resolution at startup --- # --- Unified dependency resolver: batch resolution at startup ---
# Runs unconditionally when enabled, independent of install-scripts.txt existence. # Runs unconditionally when enabled, independent of install-scripts.txt existence.
if manager_util.use_unified_resolver and not manager_util.dependency_management_enabled:
logging.info("[ComfyUI-Manager] dependency management disabled; skipping unified dependency resolver")
manager_util.use_unified_resolver = False
if manager_util.use_unified_resolver: if manager_util.use_unified_resolver:
try: try:
from .common.unified_dep_resolver import ( from .common.unified_dep_resolver import (