mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-15 01:27:05 +08:00
feat(security): Support System User Protection API with security migration (V3.38) (#2338)
- Migrate Manager data path: default/ComfyUI-Manager → __manager - Force security_level=strong on outdated ComfyUI (block installations) - Auto-migrate config.ini only; backup legacy files for manual verification - Raise weak/normal- to normal during migration - Add /manager/startup_alerts API for UI warnings - Differentiate 403 responses: comfyui_outdated vs security_level - Block startup scripts execution on old ComfyUI Requires ComfyUI v0.3.76+ for full functionality. Backward compatible with older versions (uses legacy path).
This commit is contained in:
parent
c8dce94c03
commit
aaed1dc3d5
32
README.md
32
README.md
@ -5,6 +5,7 @@
|
|||||||

|

|
||||||
|
|
||||||
## NOTICE
|
## NOTICE
|
||||||
|
* V3.38: **Security patch** - Manager data migrated to protected path. See [Migration Guide](docs/en/v3.38-userdata-security-migration.md).
|
||||||
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
|
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
|
||||||
* V3.10: `double-click feature` is removed
|
* V3.10: `double-click feature` is removed
|
||||||
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
|
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
|
||||||
@ -140,20 +141,27 @@ This repository provides Colab notebooks that allow you to install and use Comfy
|
|||||||
|
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/default/ComfyUI-Manager/`.
|
Starting from V3.38, Manager uses a protected system path for enhanced security.
|
||||||
|
|
||||||
* <USER_DIRECTORY>
|
* <USER_DIRECTORY>
|
||||||
* If executed without any options, the path defaults to ComfyUI/user.
|
* If executed without any options, the path defaults to ComfyUI/user.
|
||||||
* It can be set using --user-directory <USER_DIRECTORY>.
|
* It can be set using --user-directory <USER_DIRECTORY>.
|
||||||
|
|
||||||
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
|
| ComfyUI Version | Manager Path |
|
||||||
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
|-----------------|--------------|
|
||||||
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
| v0.3.76+ (with System User API) | `<USER_DIRECTORY>/__manager/` |
|
||||||
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
|
| Older versions | `<USER_DIRECTORY>/default/ComfyUI-Manager/` |
|
||||||
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
|
|
||||||
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
* Basic config files: `config.ini`
|
||||||
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
* Configurable channel lists: `channels.list`
|
||||||
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
* Configurable pip overrides: `pip_overrides.json`
|
||||||
|
* Configurable pip blacklist: `pip_blacklist.list`
|
||||||
|
* Configurable pip auto fix: `pip_auto_fix.list`
|
||||||
|
* Saved snapshot files: `snapshots/`
|
||||||
|
* Startup script files: `startup-scripts/`
|
||||||
|
* Component files: `components/`
|
||||||
|
|
||||||
|
> **Note**: See [Migration Guide](docs/en/v3.38-userdata-security-migration.md) for upgrade details.
|
||||||
|
|
||||||
|
|
||||||
## `extra_model_paths.yaml` Configuration
|
## `extra_model_paths.yaml` Configuration
|
||||||
|
|||||||
230
docs/en/v3.38-userdata-security-migration.md
Normal file
230
docs/en/v3.38-userdata-security-migration.md
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
# ComfyUI-Manager V3.38: Userdata Security Migration Guide
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
ComfyUI-Manager V3.38 introduces a **security patch** that migrates Manager's configuration and data to a protected system path. This change leverages ComfyUI's new System User Protection API (PR #10966) to provide enhanced security isolation.
|
||||||
|
|
||||||
|
This guide explains what happens during the migration and how to handle various situations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
### Finding Your Paths
|
||||||
|
|
||||||
|
When ComfyUI starts, it displays the full paths in the terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
** User directory: /path/to/ComfyUI/user
|
||||||
|
** ComfyUI-Manager config path: /path/to/ComfyUI/user/__manager/config.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for these lines in your startup log to find the exact location on your system. In this guide, paths are shown relative to the `user` directory.
|
||||||
|
|
||||||
|
### Path Migration
|
||||||
|
|
||||||
|
| Data | Legacy Path | New Path |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| Configuration | `user/default/ComfyUI-Manager/` | `user/__manager/` |
|
||||||
|
| Snapshots | `user/default/ComfyUI-Manager/snapshots/` | `user/__manager/snapshots/` |
|
||||||
|
|
||||||
|
### Why This Change
|
||||||
|
|
||||||
|
In older ComfyUI versions, the `default/` directory was **unprotected** and accessible via web APIs. If you ran ComfyUI with `--listen 0.0.0.0` or similar options to allow external connections, this data **may have been tampered with** by malicious actors.
|
||||||
|
|
||||||
|
**Note:** If you only used ComfyUI locally (without `--listen` or with `--listen 127.0.0.1`), your data was not exposed to this vulnerability.
|
||||||
|
|
||||||
|
The new `__manager` path uses ComfyUI's protected system directory, which:
|
||||||
|
- **Cannot be accessed** from outside (protected by ComfyUI)
|
||||||
|
- Isolates system settings from user data
|
||||||
|
- Enables stricter security for remote access
|
||||||
|
|
||||||
|
**This is why only `config.ini` is automatically migrated** - other files (snapshots) may have been compromised and should be manually verified before copying.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automatic Migration
|
||||||
|
|
||||||
|
When you start ComfyUI with the new System User Protection API, Manager automatically handles the migration:
|
||||||
|
|
||||||
|
### Step 1: Configuration Migration
|
||||||
|
|
||||||
|
Only `config.ini` is migrated automatically.
|
||||||
|
|
||||||
|
**Important**: Snapshots are **NOT** automatically migrated. You must copy them manually if needed.
|
||||||
|
|
||||||
|
### Step 2: Security Level Check
|
||||||
|
|
||||||
|
During migration, if your security level is below `normal` (i.e., `weak` or `normal-`), it will be automatically raised to `normal`. This is a safety measure because the security level setting itself may have been tampered with in the old version.
|
||||||
|
|
||||||
|
```
|
||||||
|
======================================================================
|
||||||
|
[ComfyUI-Manager] WARNING: Security level adjusted
|
||||||
|
- Previous: 'weak' → New: 'normal'
|
||||||
|
- Raised to prevent unauthorized remote access.
|
||||||
|
======================================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a lower security level, you can manually edit the config after migration.
|
||||||
|
|
||||||
|
### Step 3: Legacy Backup
|
||||||
|
|
||||||
|
Your entire legacy directory is moved to a backup location:
|
||||||
|
```
|
||||||
|
user/__manager/.legacy-manager-backup/
|
||||||
|
```
|
||||||
|
|
||||||
|
This backup is preserved until you manually delete it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistent Backup Notification
|
||||||
|
|
||||||
|
As long as the backup exists, Manager will remind you on **every startup**:
|
||||||
|
|
||||||
|
```
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
[ComfyUI-Manager] NOTICE: Legacy backup exists
|
||||||
|
- Your old Manager data was backed up to:
|
||||||
|
/path/to/ComfyUI/user/__manager/.legacy-manager-backup
|
||||||
|
- Please verify and remove it when no longer needed.
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
**To stop this notification**: Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recovering Old Data
|
||||||
|
|
||||||
|
### Snapshots
|
||||||
|
|
||||||
|
If you need your old snapshots, copy the contents of `.legacy-manager-backup/snapshots/` to `user/__manager/snapshots/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outdated ComfyUI Warning
|
||||||
|
|
||||||
|
If you're running an older version of ComfyUI without the System User Protection API, Manager will:
|
||||||
|
|
||||||
|
1. **Force security level to `strong`** - All installations are blocked
|
||||||
|
2. **Display warning message**:
|
||||||
|
|
||||||
|
```
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
[ComfyUI-Manager] ERROR: ComfyUI version is outdated!
|
||||||
|
- Most operations are blocked for security.
|
||||||
|
- ComfyUI update is still allowed.
|
||||||
|
- Please update ComfyUI to use Manager normally.
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution**: Update ComfyUI to v0.3.76 or later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Levels
|
||||||
|
|
||||||
|
| Level | What's Allowed |
|
||||||
|
|-------|----------------|
|
||||||
|
| `strong` | ComfyUI update only. All other installations blocked. |
|
||||||
|
| `normal` | Install/update/remove registered custom nodes and models. |
|
||||||
|
| `normal-` | Above + Install via Git URL or pip (localhost only). |
|
||||||
|
| `weak` | All operations allowed, including from remote connections. |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- `strong` is forced on outdated ComfyUI versions.
|
||||||
|
- `normal` is the default and recommended for most users.
|
||||||
|
- `normal-` is for developers who need to install unregistered nodes locally.
|
||||||
|
- `weak` should only be used in isolated development environments.
|
||||||
|
|
||||||
|
### Changing Security Level
|
||||||
|
|
||||||
|
Edit `user/__manager/config.ini`:
|
||||||
|
```ini
|
||||||
|
[default]
|
||||||
|
security_level = normal
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Messages
|
||||||
|
|
||||||
|
### "comfyui_outdated" (HTTP 403)
|
||||||
|
|
||||||
|
This error appears when:
|
||||||
|
- Your ComfyUI doesn't have the System User Protection API
|
||||||
|
- All installations are blocked until you update ComfyUI
|
||||||
|
|
||||||
|
**Solution**: Update ComfyUI to the latest version.
|
||||||
|
|
||||||
|
### "security_level" (HTTP 403)
|
||||||
|
|
||||||
|
This error appears when:
|
||||||
|
- Your security level blocks the requested operation
|
||||||
|
- For example, `strong` level blocks all installations
|
||||||
|
|
||||||
|
**Solution**: Lower your security level in config.ini if appropriate for your use case.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Warning: Suspicious Path
|
||||||
|
|
||||||
|
If you see this error on an **older** ComfyUI:
|
||||||
|
|
||||||
|
```
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
[ComfyUI-Manager] ERROR: Suspicious path detected!
|
||||||
|
- '__manager' exists with low security level: 'weak'
|
||||||
|
- Please verify manually:
|
||||||
|
/path/to/ComfyUI/user/__manager/config.ini
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
On older ComfyUI versions, the `__manager` directory is not normally created. If this directory exists, it may have been created externally. For safety, manually verify the contents of this directory before updating ComfyUI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### All my installations are blocked
|
||||||
|
|
||||||
|
**Check 1**: Is your ComfyUI updated?
|
||||||
|
- Old ComfyUI forces `security_level = strong`
|
||||||
|
- Update ComfyUI to resolve
|
||||||
|
|
||||||
|
**Check 2**: What's your security level?
|
||||||
|
- Check `user/__manager/config.ini`
|
||||||
|
- `security_level = strong` blocks all installations
|
||||||
|
|
||||||
|
### My snapshots are missing
|
||||||
|
|
||||||
|
Snapshots are not automatically migrated. You need to manually copy the `snapshots` folder from inside `.legacy-manager-backup` to the `user/__manager/` directory.
|
||||||
|
|
||||||
|
### I keep seeing the backup notification
|
||||||
|
|
||||||
|
Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it.
|
||||||
|
|
||||||
|
### Snapshot restore is blocked
|
||||||
|
|
||||||
|
On old ComfyUI (without System User API), snapshot restore is blocked because security is forced to `strong`. Update ComfyUI to enable snapshot restore.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
user/
|
||||||
|
└── __manager/
|
||||||
|
├── config.ini # Manager configuration
|
||||||
|
├── channels.list # Custom node channels
|
||||||
|
├── snapshots/ # Environment snapshots
|
||||||
|
└── .legacy-manager-backup/ # Backup of old Manager data (temporary)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **ComfyUI**: v0.3.76 or later (with System User Protection API)
|
||||||
|
- **ComfyUI-Manager**: V3.38 or later
|
||||||
@ -40,10 +40,11 @@ import cnr_utils
|
|||||||
import manager_util
|
import manager_util
|
||||||
import git_utils
|
import git_utils
|
||||||
import manager_downloader
|
import manager_downloader
|
||||||
|
import manager_migration
|
||||||
from node_package import InstalledNodePackage
|
from node_package import InstalledNodePackage
|
||||||
|
|
||||||
|
|
||||||
version_code = [3, 37, 2]
|
version_code = [3, 38]
|
||||||
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 '')
|
||||||
|
|
||||||
|
|
||||||
@ -214,9 +215,10 @@ def update_user_directory(user_dir):
|
|||||||
global manager_pip_blacklist_path
|
global manager_pip_blacklist_path
|
||||||
global manager_components_path
|
global manager_components_path
|
||||||
|
|
||||||
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
manager_files_path = manager_migration.get_manager_path(user_dir)
|
||||||
if not os.path.exists(manager_files_path):
|
if not os.path.exists(manager_files_path):
|
||||||
os.makedirs(manager_files_path)
|
os.makedirs(manager_files_path)
|
||||||
|
manager_migration.run_migration_checks(user_dir, manager_files_path)
|
||||||
|
|
||||||
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
|
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
|
||||||
if not os.path.exists(manager_snapshot_path):
|
if not os.path.exists(manager_snapshot_path):
|
||||||
@ -1719,7 +1721,7 @@ def read_config():
|
|||||||
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
||||||
manager_util.bypass_ssl = get_bool('bypass_ssl', False)
|
manager_util.bypass_ssl = get_bool('bypass_ssl', False)
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
||||||
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
||||||
'git_exe': default_conf.get('git_exe', ''),
|
'git_exe': default_conf.get('git_exe', ''),
|
||||||
@ -1739,6 +1741,8 @@ def read_config():
|
|||||||
'security_level': default_conf.get('security_level', 'normal').lower(),
|
'security_level': default_conf.get('security_level', 'normal').lower(),
|
||||||
'db_mode': default_conf.get('db_mode', 'cache').lower(),
|
'db_mode': default_conf.get('db_mode', 'cache').lower(),
|
||||||
}
|
}
|
||||||
|
manager_migration.force_security_level_if_needed(result)
|
||||||
|
return result
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
import importlib.util
|
import importlib.util
|
||||||
@ -1746,7 +1750,7 @@ def read_config():
|
|||||||
manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows"
|
manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows"
|
||||||
manager_util.bypass_ssl = False
|
manager_util.bypass_ssl = False
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
'http_channel_enabled': False,
|
'http_channel_enabled': False,
|
||||||
'preview_method': manager_funcs.get_current_preview_method(),
|
'preview_method': manager_funcs.get_current_preview_method(),
|
||||||
'git_exe': '',
|
'git_exe': '',
|
||||||
@ -1766,6 +1770,8 @@ def read_config():
|
|||||||
'security_level': 'normal', # strong | normal | normal- | weak
|
'security_level': 'normal', # strong | normal | normal- | weak
|
||||||
'db_mode': 'cache', # local | cache | remote
|
'db_mode': 'cache', # local | cache | remote
|
||||||
}
|
}
|
||||||
|
manager_migration.force_security_level_if_needed(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
@ -3360,8 +3366,8 @@ def get_comfyui_versions(repo=None):
|
|||||||
repo = git.Repo(comfy_path)
|
repo = git.Repo(comfy_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remote = get_remote_name(repo)
|
remote = get_remote_name(repo)
|
||||||
repo.remotes[remote].fetch()
|
repo.remotes[remote].fetch()
|
||||||
except:
|
except:
|
||||||
logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI")
|
logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI")
|
||||||
|
|
||||||
|
|||||||
356
glob/manager_migration.py
Normal file
356
glob/manager_migration.py
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
"""
|
||||||
|
ComfyUI-Manager migration module.
|
||||||
|
Handles migration from legacy paths to new __manager path structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
# Startup notices for notice board
|
||||||
|
startup_notices = [] # List of (message, level) tuples
|
||||||
|
|
||||||
|
|
||||||
|
def add_startup_notice(message, level='warning'):
|
||||||
|
"""Add a notice to be displayed on Manager notice board.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: HTML-formatted message string
|
||||||
|
level: 'warning', 'error', 'info'
|
||||||
|
"""
|
||||||
|
global startup_notices
|
||||||
|
startup_notices.append((message, level))
|
||||||
|
|
||||||
|
|
||||||
|
# Cache for API check (computed once per session)
|
||||||
|
_cached_has_system_user_api = None
|
||||||
|
|
||||||
|
|
||||||
|
def has_system_user_api():
|
||||||
|
"""Check if ComfyUI has the System User Protection API (PR #10966).
|
||||||
|
|
||||||
|
Result is cached for performance.
|
||||||
|
"""
|
||||||
|
global _cached_has_system_user_api
|
||||||
|
if _cached_has_system_user_api is None:
|
||||||
|
try:
|
||||||
|
import folder_paths
|
||||||
|
_cached_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory')
|
||||||
|
except Exception:
|
||||||
|
_cached_has_system_user_api = False
|
||||||
|
return _cached_has_system_user_api
|
||||||
|
|
||||||
|
|
||||||
|
def get_manager_path(user_dir):
|
||||||
|
"""Get the appropriate manager files path based on ComfyUI version.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: manager_files_path
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
return os.path.abspath(os.path.join(user_dir, '__manager'))
|
||||||
|
else:
|
||||||
|
return os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
||||||
|
|
||||||
|
|
||||||
|
def run_migration_checks(user_dir, manager_files_path):
|
||||||
|
"""Run all migration and security checks.
|
||||||
|
|
||||||
|
Call this after get_manager_path() to handle:
|
||||||
|
- Legacy config migration (new ComfyUI)
|
||||||
|
- Legacy backup notification (every startup)
|
||||||
|
- Suspicious directory detection (old ComfyUI)
|
||||||
|
- Outdated ComfyUI warning (old ComfyUI)
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
migrated = migrate_legacy_config(user_dir, manager_files_path)
|
||||||
|
# Only check for legacy backup if migration didn't just happen
|
||||||
|
# (migration already shows backup location in its message)
|
||||||
|
if not migrated:
|
||||||
|
check_legacy_backup(manager_files_path)
|
||||||
|
else:
|
||||||
|
check_suspicious_manager(user_dir)
|
||||||
|
warn_outdated_comfyui()
|
||||||
|
|
||||||
|
|
||||||
|
def check_legacy_backup(manager_files_path):
|
||||||
|
"""Check for legacy backup and notify user to verify and remove it.
|
||||||
|
|
||||||
|
This runs on every startup to remind users about pending legacy backup.
|
||||||
|
"""
|
||||||
|
backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup')
|
||||||
|
if not os.path.exists(backup_dir):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy backup exists")
|
||||||
|
print(" - Your old Manager data was backed up to:")
|
||||||
|
print(f" {backup_dir}")
|
||||||
|
print(" - Please verify and remove it when no longer needed.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"Legacy ComfyUI-Manager data backup exists. Please verify and remove when no longer needed.",
|
||||||
|
level='info'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_suspicious_manager(user_dir):
|
||||||
|
"""Check for suspicious __manager directory on old ComfyUI.
|
||||||
|
|
||||||
|
On old ComfyUI without System User API, if __manager exists with low security,
|
||||||
|
warn the user to verify manually.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if suspicious setup detected
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
return False # Not suspicious on new ComfyUI
|
||||||
|
|
||||||
|
suspicious_path = os.path.abspath(os.path.join(user_dir, '__manager'))
|
||||||
|
if not os.path.exists(suspicious_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
config_path = os.path.join(suspicious_path, 'config.ini')
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(config_path)
|
||||||
|
sec_level = config.get('default', 'security_level', fallback='normal').lower()
|
||||||
|
|
||||||
|
if sec_level in ['weak', 'normal-']:
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "!"*70)
|
||||||
|
print("[ComfyUI-Manager] ERROR: Suspicious path detected!")
|
||||||
|
print(f" - '__manager' exists with low security level: '{sec_level}'")
|
||||||
|
print(" - Please verify manually:")
|
||||||
|
print(f" {config_path}")
|
||||||
|
print("!"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"[Security Alert] Suspicious path detected. See terminal log for details.",
|
||||||
|
level='error'
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def warn_outdated_comfyui():
|
||||||
|
"""Warn user about outdated ComfyUI without System User API."""
|
||||||
|
if has_system_user_api():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "!"*70)
|
||||||
|
print("[ComfyUI-Manager] ERROR: ComfyUI version is outdated!")
|
||||||
|
print(" - Most operations are blocked for security.")
|
||||||
|
print(" - ComfyUI update is still allowed.")
|
||||||
|
print(" - Please update ComfyUI to use Manager normally.")
|
||||||
|
print("!"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"[Security Alert] ComfyUI outdated. Installations blocked (update allowed).<BR>"
|
||||||
|
"Update ComfyUI for normal operation.",
|
||||||
|
level='error'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_legacy_config(user_dir, manager_files_path):
|
||||||
|
"""Migrate ONLY config.ini to new __manager path if needed.
|
||||||
|
|
||||||
|
IMPORTANT: Only config.ini is migrated. Other files (snapshots, cache, etc.)
|
||||||
|
are NOT migrated - users must recreate them.
|
||||||
|
|
||||||
|
Scenarios:
|
||||||
|
1. Legacy exists, New doesn't exist → Migrate config.ini
|
||||||
|
2. Legacy exists, New exists → First update after upgrade
|
||||||
|
- Run ComfyUI dependency installation
|
||||||
|
- Rename legacy to .backup
|
||||||
|
3. Legacy doesn't exist → No migration needed
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if migration was performed
|
||||||
|
"""
|
||||||
|
if not has_system_user_api():
|
||||||
|
return False
|
||||||
|
|
||||||
|
legacy_dir = os.path.join(user_dir, 'default', 'ComfyUI-Manager')
|
||||||
|
legacy_config = os.path.join(legacy_dir, 'config.ini')
|
||||||
|
new_config = os.path.join(manager_files_path, 'config.ini')
|
||||||
|
|
||||||
|
if not os.path.exists(legacy_dir):
|
||||||
|
return False # No legacy directory, nothing to migrate
|
||||||
|
|
||||||
|
# IMPORTANT: Check for config.ini existence, not just directory
|
||||||
|
# (because makedirs() creates __manager before this function is called)
|
||||||
|
|
||||||
|
# Case: Both configs exist (first update after ComfyUI upgrade)
|
||||||
|
# This means user ran new ComfyUI at least once, creating __manager/config.ini
|
||||||
|
if os.path.exists(legacy_config) and os.path.exists(new_config):
|
||||||
|
_handle_first_update_migration(user_dir, legacy_dir, manager_files_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Case: Legacy config exists but new config doesn't (normal migration)
|
||||||
|
# This is the first run after ComfyUI upgrade
|
||||||
|
if os.path.exists(legacy_config) and not os.path.exists(new_config):
|
||||||
|
pass # Continue with normal migration below
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy config.ini detected")
|
||||||
|
print(f" - Old: {legacy_config}")
|
||||||
|
print(f" - New: {new_config}")
|
||||||
|
print(" - Migrating config.ini only (other files are NOT migrated).")
|
||||||
|
print(" - Security level below 'normal' will be raised.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
_migrate_config_with_security_check(legacy_config, new_config)
|
||||||
|
|
||||||
|
# Move legacy directory to backup
|
||||||
|
_move_legacy_to_backup(legacy_dir, manager_files_path)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_first_update_migration(user_dir, legacy_dir, manager_files_path):
|
||||||
|
"""Handle first ComfyUI update when both legacy and new directories exist.
|
||||||
|
|
||||||
|
This scenario happens when:
|
||||||
|
- User was on old ComfyUI (using default/ComfyUI-Manager)
|
||||||
|
- ComfyUI was updated (now has System User API)
|
||||||
|
- Manager already created __manager on first new run
|
||||||
|
- But legacy directory still exists
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
1. Run ComfyUI dependency installation
|
||||||
|
2. Move legacy to __manager/.legacy-manager-backup
|
||||||
|
"""
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: First update after ComfyUI upgrade detected")
|
||||||
|
print(" - Both legacy and new directories exist.")
|
||||||
|
print(" - Running ComfyUI dependency installation...")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Run ComfyUI dependency installation
|
||||||
|
# Path: glob/manager_migration.py → glob → comfyui-manager → custom_nodes → ComfyUI
|
||||||
|
try:
|
||||||
|
comfyui_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||||
|
requirements_path = os.path.join(comfyui_path, 'requirements.txt')
|
||||||
|
if os.path.exists(requirements_path):
|
||||||
|
subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', requirements_path],
|
||||||
|
capture_output=True, check=False)
|
||||||
|
print("[ComfyUI-Manager] ComfyUI dependencies installation completed.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to install ComfyUI dependencies: {e}")
|
||||||
|
|
||||||
|
# Move legacy to backup inside __manager
|
||||||
|
_move_legacy_to_backup(legacy_dir, manager_files_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _move_legacy_to_backup(legacy_dir, manager_files_path):
|
||||||
|
"""Move legacy directory to backup inside __manager.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Path to backup directory if successful, None if failed
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.exists(backup_dir):
|
||||||
|
shutil.rmtree(backup_dir) # Remove old backup if exists
|
||||||
|
shutil.move(legacy_dir, backup_dir)
|
||||||
|
|
||||||
|
# Terminal output (full paths shown here only)
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy settings migrated")
|
||||||
|
print(f" - Old location: {legacy_dir}")
|
||||||
|
print(f" - Backed up to: {backup_dir}")
|
||||||
|
print(" - Please verify and remove the backup when no longer needed.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output (no full paths for security)
|
||||||
|
add_startup_notice(
|
||||||
|
"Legacy ComfyUI-Manager data migrated. See terminal for details.",
|
||||||
|
level='info'
|
||||||
|
)
|
||||||
|
return backup_dir
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to backup legacy directory: {e}")
|
||||||
|
add_startup_notice(
|
||||||
|
f"[MIGRATION] Failed to backup legacy directory: {e}",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_config_with_security_check(legacy_path, new_path):
|
||||||
|
"""Migrate legacy config, raising security level only if below default."""
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
try:
|
||||||
|
config.read(legacy_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to parse config.ini: {e}")
|
||||||
|
print(" - Creating fresh config with default settings.")
|
||||||
|
add_startup_notice(
|
||||||
|
"[MIGRATION] Failed to parse legacy config. Using defaults.",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
return # Skip migration, let Manager create fresh config
|
||||||
|
|
||||||
|
# Security level hierarchy: strong > normal > normal- > weak
|
||||||
|
# Default is 'normal', only raise if below default
|
||||||
|
if 'default' in config:
|
||||||
|
current_level = config['default'].get('security_level', 'normal').lower()
|
||||||
|
below_default_levels = ['weak', 'normal-']
|
||||||
|
|
||||||
|
if current_level in below_default_levels:
|
||||||
|
config['default']['security_level'] = 'normal'
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("[ComfyUI-Manager] WARNING: Security level adjusted")
|
||||||
|
print(f" - Previous: '{current_level}' → New: 'normal'")
|
||||||
|
print(" - Raised to prevent unauthorized remote access.")
|
||||||
|
print("="*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
f"[MIGRATION] Security level raised: '{current_level}' → 'normal'.<BR>"
|
||||||
|
"To prevent unauthorized remote access.",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f" - Security level: '{current_level}' (no change needed)")
|
||||||
|
|
||||||
|
# Ensure directory exists
|
||||||
|
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
||||||
|
|
||||||
|
with open(new_path, 'w') as f:
|
||||||
|
config.write(f)
|
||||||
|
|
||||||
|
|
||||||
|
def force_security_level_if_needed(config_dict):
|
||||||
|
"""Force security level to 'strong' if on old ComfyUI.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dict: Configuration dictionary to modify in-place
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if security level was forced
|
||||||
|
"""
|
||||||
|
if not has_system_user_api():
|
||||||
|
config_dict['security_level'] = 'strong'
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@ -22,6 +22,7 @@ import asyncio
|
|||||||
import queue
|
import queue
|
||||||
|
|
||||||
import manager_downloader
|
import manager_downloader
|
||||||
|
import manager_migration
|
||||||
|
|
||||||
|
|
||||||
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
||||||
@ -276,6 +277,13 @@ import zipfile
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def security_403_response():
|
||||||
|
"""Return appropriate 403 response based on ComfyUI version."""
|
||||||
|
if not manager_migration.has_system_user_api():
|
||||||
|
return web.json_response({"error": "comfyui_outdated"}, status=403)
|
||||||
|
return web.json_response({"error": "security_level"}, status=403)
|
||||||
|
|
||||||
|
|
||||||
def get_model_dir(data, show_log=False):
|
def get_model_dir(data, show_log=False):
|
||||||
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
||||||
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
||||||
@ -732,7 +740,7 @@ async def fetch_updates(request):
|
|||||||
async def update_all(request):
|
async def update_all(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
with task_worker_lock:
|
with task_worker_lock:
|
||||||
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
||||||
@ -965,7 +973,7 @@ async def get_snapshot_list(request):
|
|||||||
async def remove_snapshot(request):
|
async def remove_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
target = request.rel_url.query["target"]
|
||||||
@ -983,7 +991,7 @@ async def remove_snapshot(request):
|
|||||||
async def restore_snapshot(request):
|
async def restore_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
target = request.rel_url.query["target"]
|
||||||
@ -1302,7 +1310,7 @@ async def fix_custom_node(request):
|
|||||||
async def install_custom_node_git_url(request):
|
async def install_custom_node_git_url(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high'):
|
||||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
url = await request.text()
|
url = await request.text()
|
||||||
res = await core.gitclone_install(url)
|
res = await core.gitclone_install(url)
|
||||||
@ -1322,7 +1330,7 @@ async def install_custom_node_git_url(request):
|
|||||||
async def install_custom_node_pip(request):
|
async def install_custom_node_pip(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high'):
|
||||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
packages = await request.text()
|
packages = await request.text()
|
||||||
core.pip_install(packages.split(' '))
|
core.pip_install(packages.split(' '))
|
||||||
@ -1594,6 +1602,16 @@ async def get_notice(request):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Prepend startup notices from manager_migration
|
||||||
|
for message, level in reversed(manager_migration.startup_notices):
|
||||||
|
if level == 'error':
|
||||||
|
style = 'color:red; background-color:white; font-weight:bold'
|
||||||
|
elif level == 'warning':
|
||||||
|
style = 'color:orange; background-color:white; font-weight:bold'
|
||||||
|
else:
|
||||||
|
style = 'color:blue; background-color:white'
|
||||||
|
markdown_content = f'<P style="{style}">{message}</P>' + markdown_content
|
||||||
|
|
||||||
return web.Response(text=markdown_content, status=200)
|
return web.Response(text=markdown_content, status=200)
|
||||||
else:
|
else:
|
||||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
@ -1601,11 +1619,35 @@ async def get_notice(request):
|
|||||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/startup_alerts")
|
||||||
|
async def get_startup_alerts(request):
|
||||||
|
"""Return startup alerts for customAlert display on page load.
|
||||||
|
|
||||||
|
Returns JSON array of alerts that should be shown to user immediately.
|
||||||
|
All startup notices (error, warning, info) are returned.
|
||||||
|
"""
|
||||||
|
alerts = []
|
||||||
|
|
||||||
|
# Return all startup notices for alert display
|
||||||
|
for message, level in manager_migration.startup_notices:
|
||||||
|
# Convert HTML BR to newlines for customAlert
|
||||||
|
text = message.replace('<BR>', '\n').replace('<br>', '\n')
|
||||||
|
# Add [ComfyUI-Manager] prefix for customAlert (notice board shows in Manager UI anyway)
|
||||||
|
text = text.replace('[Security Alert]', '[ComfyUI-Manager] Security Alert:')
|
||||||
|
text = text.replace('[MIGRATION]', '[ComfyUI-Manager] Migration:')
|
||||||
|
alerts.append({
|
||||||
|
'message': text,
|
||||||
|
'level': level
|
||||||
|
})
|
||||||
|
|
||||||
|
return web.json_response(alerts)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/reboot")
|
@routes.get("/manager/reboot")
|
||||||
def restart(self):
|
def restart(self):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.stdout.close_log()
|
sys.stdout.close_log()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { sleep, customConfirm, customAlert } from "./common.js";
|
import { sleep, customConfirm, customAlert, handle403Response, show_message } from "./common.js";
|
||||||
|
|
||||||
async function tryInstallCustomNode(event) {
|
async function tryInstallCustomNode(event) {
|
||||||
let msg = '-= [ComfyUI Manager] extension installation request =-\n\n';
|
let msg = '-= [ComfyUI Manager] extension installation request =-\n\n';
|
||||||
@ -42,7 +42,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(response.status == 400) {
|
else if(response.status == 400) {
|
||||||
@ -54,7 +54,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
|
|
||||||
let response = await api.fetchApi("/manager/reboot");
|
let response = await api.fetchApi("/manager/reboot");
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
|||||||
import {
|
import {
|
||||||
free_models, install_pip, install_via_git_url, manager_instance,
|
free_models, install_pip, install_via_git_url, manager_instance,
|
||||||
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
|
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
|
||||||
infoToast, showTerminal, setNeedRestart
|
infoToast, showTerminal, setNeedRestart, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
||||||
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||||
@ -753,9 +753,9 @@ async function onQueueStatus(event) {
|
|||||||
|
|
||||||
const rebootButton = document.getElementById('cm-reboot-button5');
|
const rebootButton = document.getElementById('cm-reboot-button5');
|
||||||
rebootButton?.addEventListener("click",
|
rebootButton?.addEventListener("click",
|
||||||
function() {
|
async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
manager_dialog.close();
|
manager_instance.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -780,8 +780,13 @@ async function updateAll(update_comfyui) {
|
|||||||
|
|
||||||
const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
|
const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
|
||||||
|
|
||||||
if (response.status == 401) {
|
if (response.status == 403) {
|
||||||
|
await handle403Response(response);
|
||||||
|
reset_action_buttons();
|
||||||
|
}
|
||||||
|
else if (response.status == 401) {
|
||||||
customAlert('Another task is already in progress. Please stop the ongoing task first.');
|
customAlert('Another task is already in progress. Please stop the ongoing task first.');
|
||||||
|
reset_action_buttons();
|
||||||
}
|
}
|
||||||
else if(response.status == 200) {
|
else if(response.status == 200) {
|
||||||
is_updating = true;
|
is_updating = true;
|
||||||
@ -1453,6 +1458,31 @@ app.registerExtension({
|
|||||||
|
|
||||||
load_components();
|
load_components();
|
||||||
|
|
||||||
|
// Fetch and show startup alerts (critical errors like outdated ComfyUI)
|
||||||
|
// Poll until extensionManager.toast is ready (set in Vue onMounted)
|
||||||
|
const showStartupAlerts = async () => {
|
||||||
|
let toastWaitCount = 0;
|
||||||
|
const waitForToast = () => {
|
||||||
|
if (window['app']?.extensionManager?.toast) {
|
||||||
|
fetch('/manager/startup_alerts')
|
||||||
|
.then(response => response.ok ? response.json() : [])
|
||||||
|
.then(alerts => {
|
||||||
|
for (const alert of alerts) {
|
||||||
|
customAlert(alert.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => console.warn('[ComfyUI-Manager] Failed to fetch startup alerts:', e));
|
||||||
|
} else if (toastWaitCount < 300) { // Max 30 seconds (300 * 100ms)
|
||||||
|
toastWaitCount++;
|
||||||
|
setTimeout(waitForToast, 100);
|
||||||
|
} else {
|
||||||
|
console.warn('[ComfyUI-Manager] Timeout waiting for toast. Startup alerts skipped.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
waitForToast();
|
||||||
|
};
|
||||||
|
showStartupAlerts();
|
||||||
|
|
||||||
const menu = document.querySelector(".comfy-menu");
|
const menu = document.querySelector(".comfy-menu");
|
||||||
const separator = document.createElement("hr");
|
const separator = document.createElement("hr");
|
||||||
|
|
||||||
|
|||||||
40
js/common.js
40
js/common.js
@ -100,6 +100,19 @@ export function show_message(msg) {
|
|||||||
app.ui.dialog.element.style.zIndex = 1100;
|
app.ui.dialog.element.style.zIndex = 1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handle403Response(res, defaultMessage) {
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
show_message('ComfyUI version is outdated.<BR>Please update ComfyUI to use Manager normally.');
|
||||||
|
} else {
|
||||||
|
show_message(defaultMessage || 'This action is not allowed with this security level configuration.');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
show_message(defaultMessage || 'This action is not allowed with this security level configuration.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function sleep(ms) {
|
export async function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@ -163,20 +176,23 @@ export async function customPrompt(title, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function rebootAPI() {
|
export async function rebootAPI() {
|
||||||
if ('electronAPI' in window) {
|
if ('electronAPI' in window) {
|
||||||
window.electronAPI.restartApp();
|
window.electronAPI.restartApp();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => {
|
const isConfirmed = await customConfirm("Are you sure you'd like to reboot the server?");
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
api.fetchApi("/manager/reboot");
|
const response = await api.fetchApi("/manager/reboot");
|
||||||
|
if (response.status == 403) {
|
||||||
|
await handle403Response(response);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
catch(exception) {}
|
|
||||||
}
|
}
|
||||||
});
|
catch(exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -216,7 +232,7 @@ export async function install_pip(packages) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +267,7 @@ export async function install_via_git_url(url, manager_dialog) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,9 +278,9 @@ export async function install_via_git_url(url, manager_dialog) {
|
|||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
rebootButton.addEventListener("click",
|
rebootButton.addEventListener("click",
|
||||||
function() {
|
async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
manager_dialog.close();
|
manager_instance.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
|
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
|
||||||
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
|
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
|
||||||
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
|
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
|
||||||
showPopover, hidePopover
|
showPopover, hidePopover, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
@ -1528,7 +1528,16 @@ export class CustomNodesManager {
|
|||||||
errorMsg = `'${item.title}': `;
|
errorMsg = `'${item.title}': `;
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`;
|
||||||
|
} else {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
} else if(res.status == 404) {
|
} else if(res.status == 404) {
|
||||||
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.\n`;
|
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.\n`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { $el } from "../../scripts/ui.js";
|
import { $el } from "../../scripts/ui.js";
|
||||||
import {
|
import {
|
||||||
manager_instance, rebootAPI,
|
manager_instance, rebootAPI,
|
||||||
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
|
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
|
||||||
storeColumnWidth, restoreColumnWidth, loadCss
|
storeColumnWidth, restoreColumnWidth, loadCss, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
@ -477,7 +477,16 @@ export class ModelManager {
|
|||||||
errorMsg = `'${item.name}': `;
|
errorMsg = `'${item.name}': `;
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`;
|
||||||
|
} else {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMsg += await res.text() + '\n';
|
errorMsg += await res.text() + '\n';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js"
|
import { api } from "../../scripts/api.js"
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
import { manager_instance, rebootAPI, show_message } from "./common.js";
|
import { manager_instance, rebootAPI, show_message, handle403Response } from "./common.js";
|
||||||
|
|
||||||
|
|
||||||
async function restore_snapshot(target) {
|
async function restore_snapshot(target) {
|
||||||
@ -10,7 +10,7 @@ async function restore_snapshot(target) {
|
|||||||
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ async function remove_snapshot(target) {
|
|||||||
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +145,8 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
if(btn_id) {
|
if(btn_id) {
|
||||||
const rebootButton = document.getElementById(btn_id);
|
const rebootButton = document.getElementById(btn_id);
|
||||||
const self = this;
|
const self = this;
|
||||||
rebootButton.onclick = function() {
|
rebootButton.onclick = async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
self.close();
|
self.close();
|
||||||
self.manager_dialog.close();
|
self.manager_dialog.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,7 +85,15 @@ 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_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
|
custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
|
||||||
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
|
||||||
|
# Check for System User API availability (PR #10966)
|
||||||
|
_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory')
|
||||||
|
|
||||||
|
if _has_system_user_api:
|
||||||
|
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), '__manager'))
|
||||||
|
else:
|
||||||
|
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")
|
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
||||||
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
||||||
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
|
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
|
||||||
@ -516,7 +524,8 @@ check_bypass_ssl()
|
|||||||
|
|
||||||
# Perform install
|
# Perform install
|
||||||
processed_install = set()
|
processed_install = set()
|
||||||
script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
|
# Use manager_files_path for consistency (fixes path inconsistency bug)
|
||||||
|
script_list_path = os.path.join(manager_files_path, "startup-scripts", "install-scripts.txt")
|
||||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||||
|
|
||||||
|
|
||||||
@ -793,7 +802,11 @@ def execute_startup_script():
|
|||||||
|
|
||||||
|
|
||||||
# Check if script_list_path exists
|
# Check if script_list_path exists
|
||||||
if os.path.exists(script_list_path):
|
# Block startup-scripts on old ComfyUI (security measure)
|
||||||
|
if not _has_system_user_api:
|
||||||
|
if os.path.exists(script_list_path):
|
||||||
|
print("[ComfyUI-Manager] Startup scripts blocked on old ComfyUI version.")
|
||||||
|
elif os.path.exists(script_list_path):
|
||||||
execute_startup_script()
|
execute_startup_script()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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.37.2"
|
version = "3.38"
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user