implement: support --user-directory

This commit is contained in:
Dr.Lt.Data 2024-12-19 14:49:41 +09:00
parent 602d04e236
commit 31de8ffc3d
7 changed files with 285 additions and 114 deletions

232
cm-cli.py
View File

@ -13,13 +13,30 @@ from typing_extensions import List, Annotated
import re import re
import git import git
sys.path.append(os.path.dirname(__file__)) sys.path.append(os.path.dirname(__file__))
sys.path.append(os.path.join(os.path.dirname(__file__), "glob")) sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
import manager_util
comfy_path = os.environ.get('COMFYUI_PATH')
if comfy_path is None:
try:
import folder_paths
comfy_path = os.path.join(os.path.dirname(folder_paths.__file__))
except:
comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))
sys.path.append(comfy_path)
import utils.extra_config
import cm_global import cm_global
import manager_core as core import manager_core as core
from manager_core import unified_manager from manager_core import unified_manager
import cnr_utils import cnr_utils
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__)) comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
comfy_path = os.environ.get('COMFYUI_PATH') comfy_path = os.environ.get('COMFYUI_PATH')
@ -27,21 +44,14 @@ if comfy_path is None:
print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr) print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
comfy_path = os.path.abspath(os.path.join(comfyui_manager_path, '..', '..')) comfy_path = os.path.abspath(os.path.join(comfyui_manager_path, '..', '..'))
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
custom_nodes_path = os.path.join(comfy_path, 'custom_nodes')
script_path = os.path.join(startup_script_path, "install-scripts.txt")
restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json")
pip_overrides_path = os.path.join(comfyui_manager_path, "pip_overrides.json")
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision'] cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
cm_global.pip_overrides = {} cm_global.pip_overrides = {'numpy': 'numpy<2'}
if os.path.exists(pip_overrides_path):
with open(pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")):
with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file) cm_global.pip_overrides = json.load(json_file)
cm_global.pip_overrides['numpy'] = 'numpy<2'
def check_comfyui_hash(): def check_comfyui_hash():
@ -82,6 +92,8 @@ class Ctx:
self.channel = 'default' self.channel = 'default'
self.no_deps = False self.no_deps = False
self.mode = 'cache' self.mode = 'cache'
self.user_directory = None
self.custom_nodes_paths = [os.path.join(core.comfy_path, 'custom_nodes')]
def set_channel_mode(self, channel, mode): def set_channel_mode(self, channel, mode):
if mode is not None: if mode is not None:
@ -104,14 +116,45 @@ class Ctx:
def set_no_deps(self, no_deps): def set_no_deps(self, no_deps):
self.no_deps = no_deps self.no_deps = no_deps
def set_user_directory(self, user_directory):
if user_directory is None:
return
channel_ctx = Ctx() extra_model_paths_yaml = os.path.join(user_directory, 'extra_model_paths.yaml')
if os.path.exists(extra_model_paths_yaml):
utils.extra_config.load_extra_path_config(extra_model_paths_yaml)
core.update_user_directory(user_directory)
if os.path.exists(core.manager_pip_overrides_path):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file)
@staticmethod
def get_startup_scripts_path():
return os.path.join(core.manager_startup_script_path, "install-scripts.txt")
@staticmethod
def get_restore_snapshot_path():
return os.path.join(core.manager_startup_script_path, "restore-snapshot.json")
@staticmethod
def get_snapshot_path():
return core.manager_snapshot_path
@staticmethod
def get_custom_nodes_paths():
return folder_paths.get_folder_paths('custom_nodes')
cmd_ctx = Ctx()
def install_node(node_spec_str, is_all=False, cnt_msg=''): def install_node(node_spec_str, is_all=False, cnt_msg=''):
if core.is_valid_url(node_spec_str): if core.is_valid_url(node_spec_str):
# install via urls # install via urls
res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=channel_ctx.no_deps)) res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))
if not res.result: if not res.result:
print(res.msg) print(res.msg)
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]") print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
@ -129,7 +172,7 @@ def install_node(node_spec_str, is_all=False, cnt_msg=''):
if not is_specified: if not is_specified:
version_spec = None version_spec = None
res = asyncio.run(unified_manager.install_by_id(node_name, version_spec, channel_ctx.channel, channel_ctx.mode, instant_execution=True, no_deps=channel_ctx.no_deps)) res = asyncio.run(unified_manager.install_by_id(node_name, version_spec, cmd_ctx.channel, cmd_ctx.mode, instant_execution=True, no_deps=cmd_ctx.no_deps))
if res.action == 'skip': if res.action == 'skip':
print(f"{cnt_msg} [ SKIP ] {node_name:50} => Already installed") print(f"{cnt_msg} [ SKIP ] {node_name:50} => Already installed")
@ -175,7 +218,7 @@ def fix_node(node_spec_str, is_all=False, cnt_msg=''):
node_name, version_spec, _ = node_spec node_name, version_spec, _ = node_spec
print(f"{cnt_msg} [ FIXING ]: {node_name:50}[{version_spec}]") print(f"{cnt_msg} [ FIXING ]: {node_name:50}[{version_spec}]")
res = unified_manager.unified_fix(node_name, version_spec, no_deps=channel_ctx.no_deps) res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps)
if not res.result: if not res.result:
print(f"ERROR: f{res.msg}") print(f"ERROR: f{res.msg}")
@ -215,7 +258,7 @@ def update_node(node_spec_str, is_all=False, cnt_msg=''):
node_name, version_spec, _ = node_spec node_name, version_spec, _ = node_spec
res = unified_manager.unified_update(node_name, version_spec, no_deps=channel_ctx.no_deps, return_postinstall=True) res = unified_manager.unified_update(node_name, version_spec, no_deps=cmd_ctx.no_deps, return_postinstall=True)
if not res.result: if not res.result:
print(f"ERROR: An error occurred while updating '{node_name}'.") print(f"ERROR: An error occurred while updating '{node_name}'.")
@ -333,7 +376,7 @@ def disable_node(node_spec_str: str, is_all=False, cnt_msg=''):
def show_list(kind, simple=False): def show_list(kind, simple=False):
custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=channel_ctx.channel, mode=channel_ctx.mode)) custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=cmd_ctx.channel, mode=cmd_ctx.mode))
# collect not-installed unknown nodes # collect not-installed unknown nodes
not_installed_unknown_nodes = [] not_installed_unknown_nodes = []
@ -454,7 +497,7 @@ def show_snapshot(simple_mode=False):
def show_snapshot_list(simple_mode=False): def show_snapshot_list(simple_mode=False):
snapshot_path = os.path.join(comfyui_manager_path, 'snapshots') snapshot_path = cmd_ctx.get_snapshot_path()
files = os.listdir(snapshot_path) files = os.listdir(snapshot_path)
json_files = [x for x in files if x.endswith('.json')] json_files = [x for x in files if x.endswith('.json')]
@ -463,11 +506,11 @@ def show_snapshot_list(simple_mode=False):
def cancel(): def cancel():
if os.path.exists(script_path): if os.path.exists(cmd_ctx.get_startup_scripts_path()):
os.remove(script_path) os.remove(cmd_ctx.get_startup_scripts_path())
if os.path.exists(restore_snapshot_path): if os.path.exists(cmd_ctx.get_restore_snapshot_path()):
os.remove(restore_snapshot_path) os.remove(cmd_ctx.get_restore_snapshot_path())
def auto_save_snapshot(): def auto_save_snapshot():
@ -562,9 +605,14 @@ def install(
help="Skip installing any Python dependencies", help="Skip installing any Python dependencies",
), ),
] = False, ] = False,
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
channel_ctx.set_no_deps(no_deps) cmd_ctx.set_channel_mode(channel, mode)
cmd_ctx.set_no_deps(no_deps)
for_each_nodes(nodes, act=install_node) for_each_nodes(nodes, act=install_node)
@ -592,9 +640,14 @@ def reinstall(
help="Skip installing any Python dependencies", help="Skip installing any Python dependencies",
), ),
] = False, ] = False,
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
channel_ctx.set_no_deps(no_deps) cmd_ctx.set_channel_mode(channel, mode)
cmd_ctx.set_no_deps(no_deps)
for_each_nodes(nodes, act=reinstall_node) for_each_nodes(nodes, act=reinstall_node)
@ -615,7 +668,7 @@ def uninstall(
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_channel_mode(channel, mode)
for_each_nodes(nodes, act=uninstall_node) for_each_nodes(nodes, act=uninstall_node)
@ -636,8 +689,13 @@ def update(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if 'all' in nodes: if 'all' in nodes:
auto_save_snapshot() auto_save_snapshot()
@ -667,8 +725,13 @@ def disable(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if 'all' in nodes: if 'all' in nodes:
auto_save_snapshot() auto_save_snapshot()
@ -693,8 +756,13 @@ def enable(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if 'all' in nodes: if 'all' in nodes:
auto_save_snapshot() auto_save_snapshot()
@ -719,8 +787,13 @@ def fix(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if 'all' in nodes: if 'all' in nodes:
auto_save_snapshot() auto_save_snapshot()
@ -754,6 +827,10 @@ def show(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
valid_commands = [ valid_commands = [
"installed", "installed",
@ -769,7 +846,8 @@ def show(
typer.echo(f"Invalid command: `show {arg}`", err=True) typer.echo(f"Invalid command: `show {arg}`", err=True)
exit(1) exit(1)
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if arg == 'snapshot': if arg == 'snapshot':
show_snapshot() show_snapshot()
elif arg == 'snapshot-list': elif arg == 'snapshot-list':
@ -794,6 +872,10 @@ def simple_show(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
valid_commands = [ valid_commands = [
"installed", "installed",
@ -808,7 +890,9 @@ def simple_show(
typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True) typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True)
exit(1) exit(1)
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
if arg == 'snapshot': if arg == 'snapshot':
show_snapshot(True) show_snapshot(True)
elif arg == 'snapshot-list': elif arg == 'snapshot-list':
@ -821,8 +905,15 @@ def simple_show(
def cli_only_mode( def cli_only_mode(
mode: str = typer.Argument( mode: str = typer.Argument(
..., help="[enable|disable]" ..., help="[enable|disable]"
)): ),
cli_mode_flag = os.path.join(os.path.dirname(__file__), '.enable-cli-only-mode') user_directory: str = typer.Option(
None,
help="user directory"
)
):
cmd_ctx.set_user_directory(user_directory)
cli_mode_flag = os.path.join(cmd_ctx.manager_files_directory, '.enable-cli-only-mode')
if mode.lower() == 'enable': if mode.lower() == 'enable':
with open(cli_mode_flag, 'w'): with open(cli_mode_flag, 'w'):
pass pass
@ -857,8 +948,13 @@ def deps_in_workflow(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
)
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
input_path = workflow input_path = workflow
output_path = output output_path = output
@ -867,7 +963,7 @@ def deps_in_workflow(
print(f"[bold red]File not found: {input_path}[/bold red]") print(f"[bold red]File not found: {input_path}[/bold red]")
exit(1) exit(1)
used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=channel_ctx.mode, channel_url=channel_ctx.channel)) used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=cmd_ctx.mode, channel_url=cmd_ctx.channel))
custom_nodes = {} custom_nodes = {}
for x in used_exts: for x in used_exts:
@ -894,7 +990,13 @@ def save_snapshot(
show_default=False, help="Specify the output file path. (.json/.yaml)" show_default=False, help="Specify the output file path. (.json/.yaml)"
), ),
] = None, ] = None,
user_directory: str = typer.Option(
None,
help="user directory"
)
): ):
cmd_ctx.set_user_directory(user_directory)
path = core.save_snapshot_with_postfix('snapshot', output) path = core.save_snapshot_with_postfix('snapshot', output)
print(f"Current snapshot is saved as `{path}`") print(f"Current snapshot is saved as `{path}`")
@ -920,7 +1022,13 @@ def restore_snapshot(
is_flag=True, is_flag=True,
help="Restore for pip packages specified by local paths.", help="Restore for pip packages specified by local paths.",
), ),
user_directory: str = typer.Option(
None,
help="user directory"
)
): ):
cmd_ctx.set_user_directory(user_directory)
extras = [] extras = []
if pip_non_url: if pip_non_url:
extras.append('--pip-non-url') extras.append('--pip-non-url')
@ -936,7 +1044,7 @@ def restore_snapshot(
if os.path.exists(snapshot_name): if os.path.exists(snapshot_name):
snapshot_path = os.path.abspath(snapshot_name) snapshot_path = os.path.abspath(snapshot_name)
else: else:
snapshot_path = os.path.join(core.comfyui_manager_path, 'snapshots', snapshot_name) snapshot_path = os.path.join(cmd_ctx.get_snapshot_path(), snapshot_name)
if not os.path.exists(snapshot_path): if not os.path.exists(snapshot_path):
print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]") print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]")
exit(1) exit(1)
@ -952,9 +1060,21 @@ def restore_snapshot(
@app.command( @app.command(
"restore-dependencies", help="Restore dependencies from whole installed custom nodes." "restore-dependencies", help="Restore dependencies from whole installed custom nodes."
) )
def restore_dependencies(): def restore_dependencies(
node_paths = [os.path.join(custom_nodes_path, name) for name in os.listdir(custom_nodes_path) user_directory: str = typer.Option(
if os.path.isdir(os.path.join(custom_nodes_path, name)) and not name.endswith('.disabled')] None,
help="user directory"
)
):
cmd_ctx.set_user_directory(user_directory)
node_paths = []
for base_path in cmd_ctx.get_custom_nodes_paths():
for name in os.listdir(base_path):
target = os.path.join(base_path, name)
if os.path.isdir(target) and not name.endswith('.disabled'):
node_paths.append(target)
total = len(node_paths) total = len(node_paths)
i = 1 i = 1
@ -971,7 +1091,8 @@ def restore_dependencies():
def post_install( def post_install(
path: str = typer.Argument( path: str = typer.Argument(
help="path to custom node", help="path to custom node",
)): )
):
path = os.path.expanduser(path) path = os.path.expanduser(path)
unified_manager.execute_install_script('', path, instant_execution=True) unified_manager.execute_install_script('', path, instant_execution=True)
@ -995,8 +1116,13 @@ def install_deps(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
), ),
user_directory: str = typer.Option(
None,
help="user directory"
),
): ):
channel_ctx.set_channel_mode(channel, mode) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
auto_save_snapshot() auto_save_snapshot()
if not os.path.exists(deps): if not os.path.exists(deps):
@ -1040,14 +1166,20 @@ def export_custom_node_ids(
mode: str = typer.Option( mode: str = typer.Option(
None, None,
help="[remote|local|cache]" help="[remote|local|cache]"
)): ),
channel_ctx.set_channel_mode(channel, mode) user_directory: str = typer.Option(
None,
help="user directory"
),
):
cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
with open(path, "w", encoding='utf-8') as output_file: with open(path, "w", encoding='utf-8') as output_file:
for x in unified_manager.cnr_map.keys(): for x in unified_manager.cnr_map.keys():
print(x, file=output_file) print(x, file=output_file)
custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=channel_ctx.channel, mode=channel_ctx.mode)) custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=cmd_ctx.channel, mode=cmd_ctx.mode))
for x in custom_nodes.values(): for x in custom_nodes.values():
if 'cnr_latest' not in x: if 'cnr_latest' not in x:
if len(x['files']) == 1: if len(x['files']) == 1:
@ -1063,7 +1195,13 @@ def export_custom_node_ids(
"migrate", "migrate",
help="Migrate legacy node system to new node system", help="Migrate legacy node system to new node system",
) )
def migrate(): def migrate(
user_directory: str = typer.Option(
None,
help="user directory"
)
):
cmd_ctx.set_user_directory(user_directory)
asyncio.run(unified_manager.migrate_unmanaged_nodes()) asyncio.run(unified_manager.migrate_unmanaged_nodes())

View File

@ -326,20 +326,18 @@ def invalidate_custom_node_file(file_custom_node_infos):
download_url(url, working_directory) download_url(url, working_directory)
def apply_snapshot(target): def apply_snapshot(path):
try: try:
# todo: should be if target is not in snapshots dir
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{target}")
if os.path.exists(path): if os.path.exists(path):
if not target.endswith('.json') and not target.endswith('.yaml'): if not path.endswith('.json') and not path.endswith('.yaml'):
print(f"Snapshot file not found: `{path}`") print(f"Snapshot file not found: `{path}`")
print("APPLY SNAPSHOT: False") print("APPLY SNAPSHOT: False")
return None return None
with open(path, 'r', encoding="UTF-8") as snapshot_file: with open(path, 'r', encoding="UTF-8") as snapshot_file:
if target.endswith('.json'): if path.endswith('.json'):
info = json.load(snapshot_file) info = json.load(snapshot_file)
elif target.endswith('.yaml'): elif path.endswith('.yaml'):
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader) info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
info = info['custom_nodes'] info = info['custom_nodes']
else: else:

View File

@ -34,7 +34,7 @@ import manager_util
import manager_downloader import manager_downloader
version_code = [3, 0] version_code = [3, 1]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
@ -89,7 +89,6 @@ def check_invalid_nodes():
try: try:
import folder_paths import folder_paths
node_paths = folder_paths.get_folder_paths("custom_nodes")
except: except:
try: try:
sys.path.append(comfy_path) sys.path.append(comfy_path)
@ -140,19 +139,50 @@ if comfy_path is None:
comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..')) comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))
channel_list_template_path = os.path.join(manager_util.comfyui_manager_path, 'channels.list.template')
git_script_path = os.path.join(manager_util.comfyui_manager_path, "git_helper.py")
manager_files_path = None
manager_config_path = None
manager_channel_list_path = None
manager_startup_script_path = None
manager_snapshot_path = None
manager_pip_overrides_path = None
def update_user_directory(user_dir):
global manager_files_path
global manager_config_path
global manager_channel_list_path
global manager_startup_script_path
global manager_snapshot_path
global manager_pip_overrides_path
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
if not os.path.exists(manager_files_path):
os.makedirs(manager_files_path)
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
if not os.path.exists(manager_snapshot_path):
os.makedirs(manager_snapshot_path)
manager_startup_script_path = os.path.join(manager_files_path, "startup-scripts")
if not os.path.exists(manager_startup_script_path):
os.makedirs(manager_startup_script_path)
manager_config_path = os.path.join(manager_files_path, 'config.ini')
manager_channel_list_path = os.path.join(manager_files_path, 'channels.list')
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
try: try:
import folder_paths import folder_paths
manager_core_config_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'manager-core.ini')) update_user_directory(folder_paths.get_user_directory())
except Exception: except Exception:
# fallback: # fallback:
# This case is only possible when running with cm-cli, and in practice, this case is not actually used. # This case is only possible when running with cm-cli, and in practice, this case is not actually used.
manager_core_config_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, 'manager-core.ini')) update_user_directory(os.path.abspath(manager_util.comfyui_manager_path))
channel_list_path = os.path.join(manager_util.comfyui_manager_path, 'channels.list')
config_path = os.path.join(manager_util.comfyui_manager_path, "config.ini")
startup_script_path = os.path.join(manager_util.comfyui_manager_path, "startup-scripts")
git_script_path = os.path.join(manager_util.comfyui_manager_path, "git_helper.py")
cached_config = None cached_config = None
js_path = None js_path = None
@ -785,7 +815,7 @@ class UnifiedManager:
return True return True
def reserve_cnr_switch(self, target, zip_url, from_path, to_path, no_deps): def reserve_cnr_switch(self, target, zip_url, from_path, to_path, no_deps):
script_path = os.path.join(startup_script_path, "install-scripts.txt") script_path = os.path.join(manager_startup_script_path, "install-scripts.txt")
with open(script_path, "a") as file: with open(script_path, "a") as file:
obj = [target, "#LAZY-CNR-SWITCH-SCRIPT", zip_url, from_path, to_path, no_deps, get_default_custom_nodes_path(), sys.executable] obj = [target, "#LAZY-CNR-SWITCH-SCRIPT", zip_url, from_path, to_path, no_deps, get_default_custom_nodes_path(), sys.executable]
file.write(f"{obj}\n") file.write(f"{obj}\n")
@ -795,7 +825,7 @@ class UnifiedManager:
return True return True
def reserve_migration(self, moves): def reserve_migration(self, moves):
script_path = os.path.join(startup_script_path, "install-scripts.txt") script_path = os.path.join(manager_startup_script_path, "install-scripts.txt")
with open(script_path, "a") as file: with open(script_path, "a") as file:
obj = ["", "#LAZY-MIGRATION", moves] obj = ["", "#LAZY-MIGRATION", moves]
file.write(f"{obj}\n") file.write(f"{obj}\n")
@ -1402,10 +1432,10 @@ def get_channel_dict():
if channel_dict is None: if channel_dict is None:
channel_dict = {} channel_dict = {}
if not os.path.exists(channel_list_path): if not os.path.exists(manager_channel_list_path):
shutil.copy(channel_list_path+'.template', channel_list_path) shutil.copy(channel_list_template_path, manager_channel_list_path)
with open(os.path.join(manager_util.comfyui_manager_path, 'channels.list'), 'r') as file: with open(manager_channel_list_path, 'r') as file:
channels = file.read() channels = file.read()
for x in channels.split('\n'): for x in channels.split('\n'):
channel_info = x.split("::") channel_info = x.split("::")
@ -1468,18 +1498,18 @@ def write_config():
'skip_migration_check': get_config()['skip_migration_check'], 'skip_migration_check': get_config()['skip_migration_check'],
} }
directory = os.path.dirname(manager_core_config_path) directory = os.path.dirname(manager_config_path)
if not os.path.exists(directory): if not os.path.exists(directory):
os.makedirs(directory) os.makedirs(directory)
with open(manager_core_config_path, 'w') as configfile: with open(manager_config_path, 'w') as configfile:
config.write(configfile) config.write(configfile)
def read_config(): def read_config():
try: try:
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(config_path) config.read(manager_config_path)
default_conf = config['default'] default_conf = config['default']
# policy migration: disable_unsecure_features -> security_level # policy migration: disable_unsecure_features -> security_level
@ -1545,10 +1575,10 @@ def switch_to_default_branch(repo):
def try_install_script(url, repo_path, install_cmd, instant_execution=False): def try_install_script(url, repo_path, install_cmd, instant_execution=False):
if not instant_execution and ((len(install_cmd) > 0 and install_cmd[0].startswith('#')) or (platform.system() == "Windows" and comfy_ui_commit_datetime.date() >= comfy_ui_required_commit_datetime.date())): if not instant_execution and ((len(install_cmd) > 0 and install_cmd[0].startswith('#')) or (platform.system() == "Windows" and comfy_ui_commit_datetime.date() >= comfy_ui_required_commit_datetime.date())):
if not os.path.exists(startup_script_path): if not os.path.exists(manager_startup_script_path):
os.makedirs(startup_script_path) os.makedirs(manager_startup_script_path)
script_path = os.path.join(startup_script_path, "install-scripts.txt") script_path = os.path.join(manager_startup_script_path, "install-scripts.txt")
with open(script_path, "a") as file: with open(script_path, "a") as file:
obj = [repo_path] + install_cmd obj = [repo_path] + install_cmd
file.write(f"{obj}\n") file.write(f"{obj}\n")
@ -2377,7 +2407,7 @@ def save_snapshot_with_postfix(postfix, path=None):
date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S")
file_name = f"{date_time_format}_{postfix}" file_name = f"{date_time_format}_{postfix}"
path = os.path.join(manager_util.comfyui_manager_path, 'snapshots', f"{file_name}.json") path = os.path.join(manager_snapshot_path, f"{file_name}.json")
else: else:
file_name = path.replace('\\', '/').split('/')[-1] file_name = path.replace('\\', '/').split('/')[-1]
file_name = file_name.split('.')[-2] file_name = file_name.split('.')[-2]

View File

@ -634,8 +634,7 @@ async def fetch_externalmodel_list(request):
@PromptServer.instance.routes.get("/snapshot/getlist") @PromptServer.instance.routes.get("/snapshot/getlist")
async def get_snapshot_list(request): async def get_snapshot_list(request):
snapshots_directory = os.path.join(manager_util.comfyui_manager_path, 'snapshots') items = [f[:-5] for f in os.listdir(core.manager_snapshot_path) if f.endswith('.json')]
items = [f[:-5] for f in os.listdir(snapshots_directory) if f.endswith('.json')]
items.sort(reverse=True) items.sort(reverse=True)
return web.json_response({'items': items}, content_type='application/json') return web.json_response({'items': items}, content_type='application/json')
@ -649,7 +648,7 @@ async def remove_snapshot(request):
try: try:
target = request.rel_url.query["target"] target = request.rel_url.query["target"]
path = os.path.join(manager_util.comfyui_manager_path, 'snapshots', f"{target}.json") path = os.path.join(core.manager_snapshot_path, f"{target}.json")
if os.path.exists(path): if os.path.exists(path):
os.remove(path) os.remove(path)
@ -667,12 +666,12 @@ async def restore_snapshot(request):
try: try:
target = request.rel_url.query["target"] target = request.rel_url.query["target"]
path = os.path.join(manager_util.comfyui_manager_path, 'snapshots', f"{target}.json") path = os.path.join(core.manager_snapshot_path, f"{target}.json")
if os.path.exists(path): if os.path.exists(path):
if not os.path.exists(core.startup_script_path): if not os.path.exists(core.manager_startup_script_path):
os.makedirs(core.startup_script_path) os.makedirs(core.manager_startup_script_path)
target_path = os.path.join(core.startup_script_path, "restore-snapshot.json") target_path = os.path.join(core.manager_startup_script_path, "restore-snapshot.json")
shutil.copy(path, target_path) shutil.copy(path, target_path)
print(f"Snapshot restore scheduled: `{target}`") print(f"Snapshot restore scheduled: `{target}`")
@ -1399,7 +1398,7 @@ async def default_cache_update():
threading.Thread(target=lambda: asyncio.run(default_cache_update())).start() threading.Thread(target=lambda: asyncio.run(default_cache_update())).start()
if not os.path.exists(core.config_path): if not os.path.exists(core.manager_config_path):
core.get_config() core.get_config()
core.write_config() core.write_config()
@ -1408,5 +1407,5 @@ cm_global.register_extension('ComfyUI-Manager',
{'version': core.version, {'version': core.version,
'name': 'ComfyUI Manager', 'name': 'ComfyUI Manager',
'nodes': {}, 'nodes': {},
'description': 'It provides the ability to manage custom nodes in ComfyUI.', }) 'description': 'This extension provides the ability to manage custom nodes in ComfyUI.', })

View File

@ -65,10 +65,10 @@ async def share_option(request):
def get_openart_auth(): def get_openart_auth():
if not os.path.exists(os.path.join(core.comfyui_manager_path, ".openart_key")): if not os.path.exists(os.path.join(core.manager_files_path, ".openart_key")):
return None return None
try: try:
with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "r") as f: with open(os.path.join(core.manager_files_path, ".openart_key"), "r") as f:
openart_key = f.read().strip() openart_key = f.read().strip()
return openart_key if openart_key else None return openart_key if openart_key else None
except: except:
@ -76,10 +76,10 @@ def get_openart_auth():
def get_matrix_auth(): def get_matrix_auth():
if not os.path.exists(os.path.join(core.comfyui_manager_path, "matrix_auth")): if not os.path.exists(os.path.join(core.manager_files_path, "matrix_auth")):
return None return None
try: try:
with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "r") as f: with open(os.path.join(core.manager_files_path, "matrix_auth"), "r") as f:
matrix_auth = f.read() matrix_auth = f.read()
homeserver, username, password = matrix_auth.strip().split("\n") homeserver, username, password = matrix_auth.strip().split("\n")
if not homeserver or not username or not password: if not homeserver or not username or not password:
@ -94,10 +94,10 @@ def get_matrix_auth():
def get_comfyworkflows_auth(): def get_comfyworkflows_auth():
if not os.path.exists(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey")): if not os.path.exists(os.path.join(core.manager_files_path, "comfyworkflows_sharekey")):
return None return None
try: try:
with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "r") as f: with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "r") as f:
share_key = f.read() share_key = f.read()
if not share_key.strip(): if not share_key.strip():
return None return None
@ -107,10 +107,10 @@ def get_comfyworkflows_auth():
def get_youml_settings(): def get_youml_settings():
if not os.path.exists(os.path.join(core.comfyui_manager_path, ".youml")): if not os.path.exists(os.path.join(core.manager_files_path, ".youml")):
return None return None
try: try:
with open(os.path.join(core.comfyui_manager_path, ".youml"), "r") as f: with open(os.path.join(core.manager_files_path, ".youml"), "r") as f:
youml_settings = f.read().strip() youml_settings = f.read().strip()
return youml_settings if youml_settings else None return youml_settings if youml_settings else None
except: except:
@ -118,7 +118,7 @@ def get_youml_settings():
def set_youml_settings(settings): def set_youml_settings(settings):
with open(os.path.join(core.comfyui_manager_path, ".youml"), "w") as f: with open(os.path.join(core.manager_files_path, ".youml"), "w") as f:
f.write(settings) f.write(settings)
@ -135,7 +135,7 @@ async def api_get_openart_auth(request):
async def api_set_openart_auth(request): async def api_set_openart_auth(request):
json_data = await request.json() json_data = await request.json()
openart_key = json_data['openart_key'] openart_key = json_data['openart_key']
with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "w") as f: with open(os.path.join(core.manager_files_path, ".openart_key"), "w") as f:
f.write(openart_key) f.write(openart_key)
return web.Response(status=200) return web.Response(status=200)
@ -178,14 +178,14 @@ async def api_get_comfyworkflows_auth(request):
@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images") @PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images")
async def set_esheep_workflow_and_images(request): async def set_esheep_workflow_and_images(request):
json_data = await request.json() json_data = await request.json()
with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), "w", encoding='utf-8') as file: with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
json.dump(json_data, file, indent=4) json.dump(json_data, file, indent=4)
return web.Response(status=200) return web.Response(status=200)
@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images") @PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images")
async def get_esheep_workflow_and_images(request): async def get_esheep_workflow_and_images(request):
with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file: with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
data = json.load(file) data = json.load(file)
return web.Response(status=200, text=json.dumps(data)) return web.Response(status=200, text=json.dumps(data))
@ -194,12 +194,12 @@ def set_matrix_auth(json_data):
homeserver = json_data['homeserver'] homeserver = json_data['homeserver']
username = json_data['username'] username = json_data['username']
password = json_data['password'] password = json_data['password']
with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "w") as f: with open(os.path.join(core.manager_files_path, "matrix_auth"), "w") as f:
f.write("\n".join([homeserver, username, password])) f.write("\n".join([homeserver, username, password]))
def set_comfyworkflows_auth(comfyworkflows_sharekey): def set_comfyworkflows_auth(comfyworkflows_sharekey):
with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "w") as f: with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "w") as f:
f.write(comfyworkflows_sharekey) f.write(comfyworkflows_sharekey)

View File

@ -18,6 +18,7 @@ import manager_util
import cm_global import cm_global
import manager_downloader import manager_downloader
from datetime import datetime from datetime import datetime
import folder_paths
security_check.security_check() security_check.security_check()
@ -73,17 +74,19 @@ cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extensi
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__)) comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, ".."))
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts") custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json") manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py") git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py") cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py")
pip_overrides_path = os.path.join(comfyui_manager_path, "pip_overrides.json")
cm_global.pip_overrides = {} cm_global.pip_overrides = {}
if os.path.exists(pip_overrides_path): if os.path.exists(manager_pip_overrides_path):
with open(pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file) cm_global.pip_overrides = json.load(json_file)
cm_global.pip_overrides['numpy'] = 'numpy<2' cm_global.pip_overrides['numpy'] = 'numpy<2'
cm_global.pip_overrides['ultralytics'] = 'ultralytics==8.3.40' # for security cm_global.pip_overrides['ultralytics'] = 'ultralytics==8.3.40' # for security
@ -146,15 +149,18 @@ try:
postfix = "" postfix = ""
# Logger setup # Logger setup
log_path_base = None
if enable_file_logging: if enable_file_logging:
if os.path.exists(f"comfyui{postfix}.log"): log_path_base = os.path.join(folder_paths.user_directory, 'comfyui')
if os.path.exists(f"comfyui{postfix}.prev.log"):
if os.path.exists(f"comfyui{postfix}.prev2.log"):
os.remove(f"comfyui{postfix}.prev2.log")
os.rename(f"comfyui{postfix}.prev.log", f"comfyui{postfix}.prev2.log")
os.rename(f"comfyui{postfix}.log", f"comfyui{postfix}.prev.log")
log_file = open(f"comfyui{postfix}.log", "w", encoding="utf-8", errors="ignore") if os.path.exists(f"{log_path_base}{postfix}.log"):
if os.path.exists(f"{log_path_base}{postfix}.prev.log"):
if os.path.exists(f"{log_path_base}{postfix}.prev2.log"):
os.remove(f"{log_path_base}{postfix}.prev2.log")
os.rename(f"{log_path_base}{postfix}.prev.log", f"{log_path_base}{postfix}.prev2.log")
os.rename(f"{log_path_base}{postfix}.log", f"{log_path_base}{postfix}.prev.log")
log_file = open(f"{log_path_base}{postfix}.log", "w", encoding="utf-8", errors="ignore")
log_lock = threading.Lock() log_lock = threading.Lock()
@ -339,8 +345,8 @@ print("** Python version:", sys.version)
print("** Python executable:", sys.executable) print("** Python executable:", sys.executable)
print("** ComfyUI Path:", comfy_path) print("** ComfyUI Path:", comfy_path)
if enable_file_logging: if log_path_base is not None:
print("** Log path:", os.path.abspath('comfyui.log')) print("** Log path:", os.path.abspath(f'{log_path_base}.log'))
else: else:
print("** Log path: file logging is disabled") print("** Log path: file logging is disabled")
@ -386,7 +392,7 @@ check_bypass_ssl()
# Perform install # Perform install
processed_install = set() processed_install = set()
script_list_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "startup-scripts", "install-scripts.txt") script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages()) pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages())
@ -463,7 +469,7 @@ if os.path.exists(restore_snapshot_path):
new_env["COMFYUI_PATH"] = comfy_path new_env["COMFYUI_PATH"] = comfy_path
cmd_str = [sys.executable, cm_cli_path, 'restore-snapshot', restore_snapshot_path] cmd_str = [sys.executable, cm_cli_path, 'restore-snapshot', restore_snapshot_path]
exit_code = process_wrap(cmd_str, custom_nodes_path, handler=msg_capture, env=new_env) exit_code = process_wrap(cmd_str, custom_nodes_base_path, handler=msg_capture, env=new_env)
if exit_code != 0: if exit_code != 0:
print("[ComfyUI-Manager] Restore snapshot failed.") print("[ComfyUI-Manager] Restore snapshot failed.")

View File

@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-manager" name = "comfyui-manager"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI." description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
version = "3.0" version = "3.1"
license = { file = "LICENSE.txt" } license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"] dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]