mirror of
https://github.com/vladmandic/sdnext.git
synced 2026-10-12 02:24:13 +00:00
update allowed paths handling and document torch versions
Signed-off-by: Vladimir Mandic <[email protected]>
This commit is contained in:
+23
-4
@@ -2,10 +2,29 @@
|
||||
|
||||
## Update for 2026-10-04
|
||||
|
||||
- sdnq: support double-quant, including `nvfp4`
|
||||
- schedulers: update init signature for new configuration options
|
||||
- rocm: remove incompatible env flags
|
||||
- test: update `--test` workflow
|
||||
- **Torch**
|
||||
update all torch versions to best known options:
|
||||
- **cuda**: `torch==2.14.1+cu132`
|
||||
- **rocm/linux**: `torch==2.14.1+rocm7.14`
|
||||
- **rocm/windows**: `torch==2.12.0+rocm10.0.0`
|
||||
- **xpu**: `torch==2.14.1+xpu`
|
||||
- **openvino**: `torch==2.11.0+cpu` with `openvino==2026.4.1`
|
||||
- **zluda**: `torch==2.7.1+cu118`
|
||||
|
||||
notes:
|
||||
- if you want to manually install `torch`, sdnext will not interfere/change it
|
||||
- sdnext only updates `torch` if its not already installed
|
||||
for best experience use `--reinstall` flags to force updates
|
||||
- if you want to try cutting-edge versions of torch
|
||||
use the `--use-nightly` flag
|
||||
- zluda should only be used on older amd gpus not supported by rocm/windows
|
||||
- **Compute**
|
||||
- sdnq: support double-quant, including `nvfp4`
|
||||
- **Fixes**
|
||||
- schedulers: update init signature for new configuration options
|
||||
- rocm: remove incompatible env flags
|
||||
- test: update `--test` workflow
|
||||
- paths: improve handling of allowed paths and add settings option to extend them
|
||||
|
||||
## Update for 2026-10-01
|
||||
|
||||
|
||||
+1
-1
Submodule extensions-builtin/sdnq updated: cc2e5b9002...21c74295fd
+1
-1
@@ -677,7 +677,7 @@ def install_cuda():
|
||||
if args.use_nightly:
|
||||
cmd = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu132 --extra-index-url https://download.pytorch.org/whl/nightly/cu130')
|
||||
else:
|
||||
cmd = os.environ.get('TORCH_COMMAND', 'torch==2.14.0+cu132 torchvision==0.29.0+cu132 --index-url https://download.pytorch.org/whl/cu132')
|
||||
cmd = os.environ.get('TORCH_COMMAND', 'torch==2.14.1+cu132 torchvision==0.29.1+cu132 --index-url https://download.pytorch.org/whl/cu132')
|
||||
return cmd
|
||||
|
||||
|
||||
|
||||
+12
-4
@@ -176,10 +176,13 @@ def register_api(api): # register api
|
||||
# Build list of resolved output paths with labels
|
||||
folders = []
|
||||
if base_samples:
|
||||
folders.append(make_folder(base_samples, os.path.basename(base_samples.rstrip('/\\'))))
|
||||
if base_grids and base_grids != base_samples:
|
||||
folders.append(make_folder(base_grids, os.path.basename(base_grids.rstrip('/\\'))))
|
||||
base_samples = base_samples.rstrip('/\\')
|
||||
folders.append(make_folder(resolve_output_path(base_samples), "Folders"))
|
||||
if base_grids:
|
||||
base_grids = base_grids.rstrip('/\\')
|
||||
folders.append(make_folder(resolve_output_path(base_grids), "Folders"))
|
||||
# Use the specific folder setting values as labels (e.g., "outputs/text" -> "outputs/text")
|
||||
folders.append(make_folder(reference_dir, "Reference"))
|
||||
folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_txt2img_samples), shared.opts.outdir_txt2img_samples))
|
||||
folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_img2img_samples), shared.opts.outdir_img2img_samples))
|
||||
folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_control_samples), shared.opts.outdir_control_samples))
|
||||
@@ -190,6 +193,7 @@ def register_api(api): # register api
|
||||
folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_txt2img_grids), shared.opts.outdir_txt2img_grids))
|
||||
folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_img2img_grids), shared.opts.outdir_img2img_grids))
|
||||
folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_control_grids), shared.opts.outdir_control_grids))
|
||||
|
||||
# Custom browser folders and reference dir
|
||||
for f in shared.opts.browser_folders.split(','):
|
||||
f = f.strip()
|
||||
@@ -204,9 +208,11 @@ def register_api(api): # register api
|
||||
if path and path not in seen_paths and os.path.isdir(path):
|
||||
seen_paths.add(path)
|
||||
unique_folders.append(f)
|
||||
if shared.demo is not None and path not in shared.demo.allowed_paths:
|
||||
if (shared.demo is not None) and (path not in shared.demo.allowed_paths):
|
||||
debug(f'Browser folders allow: {path}')
|
||||
shared.demo.allowed_paths.append(path)
|
||||
if os.environ.get('SD_PATH_DEBUG', None):
|
||||
log.trace(f'Path allowed: why=gallery allowed={shared.demo.allowed_paths}')
|
||||
debug(f'Browser folders: {unique_folders}')
|
||||
return unique_folders
|
||||
|
||||
@@ -275,3 +281,5 @@ def register_api(api): # register api
|
||||
except Exception as e:
|
||||
debug(f'WS error: {e}')
|
||||
manager.disconnect(ws)
|
||||
|
||||
get_folders() # initialize all folders
|
||||
|
||||
@@ -13,6 +13,7 @@ from modules.logger import log
|
||||
|
||||
_upload_store_getter = None
|
||||
MAX_B64_BYTES = 256 * 1024 * 1024 # base64 expands ~4/3 and the response is built in memory; larger artifacts are fetched by path instead
|
||||
debug = os.environ.get('SD_PATH_DEBUG', None) is not None
|
||||
|
||||
|
||||
def register_upload_store(getter_fn):
|
||||
@@ -204,15 +205,20 @@ def validate_path(fn: str, allowed_dirs: list[str] | None = None, allowed_folder
|
||||
decoded = unquote(fn).replace('%3A', ':')
|
||||
sanitized = sanitize_filename(decoded)
|
||||
resolved = Path(sanitized).resolve()
|
||||
# log.trace(f'API validate: fn="{fn}" sanitized="{sanitized}" resolved="{resolved}" parents={resolved.parents} allowed={allowed}')
|
||||
if not any(folder in resolved.parents for folder in allowed):
|
||||
if debug:
|
||||
log.error(f'API validate allowed: fn="{fn}" sanitized="{sanitized}" resolved="{resolved}" parents={resolved.parents} allowed={allowed}')
|
||||
raise HTTPException(status_code=403, detail=f"file not allowed: {resolved}")
|
||||
if resolved.is_dir():
|
||||
if allowed_folder:
|
||||
return str(resolved)
|
||||
if debug:
|
||||
log.error(f'API validate folder: fn="{fn}" sanitized="{sanitized}" resolved="{resolved}" parents={resolved.parents} allowed={allowed}')
|
||||
raise HTTPException(status_code=403, detail=f"directory not allowed: {resolved}")
|
||||
if not resolved.is_file():
|
||||
if allowed_file:
|
||||
return str(resolved)
|
||||
if debug:
|
||||
log.error(f'API validate file:: fn="{fn}" sanitized="{sanitized}" resolved="{resolved}" parents={resolved.parents} allowed={allowed}')
|
||||
raise HTTPException(status_code=404, detail=f"file not found: {resolved}")
|
||||
return str(resolved)
|
||||
|
||||
+20
-22
@@ -45,7 +45,7 @@ sd_configs_path = os.path.join(script_path, "configs")
|
||||
sd_default_config = os.path.join(sd_configs_path, "v1-inference.yaml")
|
||||
sd_model_file = cli.ckpt or os.path.join(script_path, 'model.safetensors') # not used
|
||||
default_sd_model_file = sd_model_file # not used
|
||||
debug = log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
debug = log.debug if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
debug('Trace: PATH')
|
||||
paths = {}
|
||||
|
||||
@@ -65,16 +65,15 @@ def create_path(folder):
|
||||
log.error(f'Create failed: folder="{folder}" {e}')
|
||||
|
||||
|
||||
def resolve_output_path(base_path: str, specific_path: str) -> str:
|
||||
def resolve_output_path(base_path: str, specific_path: str | None = None) -> str:
|
||||
"""
|
||||
Resolve output path by combining base and specific paths.
|
||||
|
||||
- If specific_path is absolute, return it directly (base is ignored)
|
||||
- If base_path is set and specific_path is relative, join them
|
||||
- If base_path is empty/None, return specific_path as-is
|
||||
"""
|
||||
if not specific_path:
|
||||
return base_path or ''
|
||||
return os.path.normpath(base_path)
|
||||
if os.path.isabs(specific_path):
|
||||
return specific_path
|
||||
if base_path:
|
||||
@@ -123,29 +122,16 @@ def create_paths(opts):
|
||||
create_path(fix_path('onnx_temp_dir'))
|
||||
create_path(fix_path('outdir_samples'))
|
||||
create_path(fix_path('outdir_grids'))
|
||||
# per-type output dirs resolve against data_path via fix_path(), so create them bare
|
||||
# only when no base folder is set; with a base configured, the resolved base+specific
|
||||
# paths below are the real targets and the bare versions would just litter data_path
|
||||
base_samples = opts.data.get('outdir_samples', '')
|
||||
base_grids = opts.data.get('outdir_grids', '')
|
||||
if not base_samples:
|
||||
create_path(fix_path('outdir_txt2img_samples'))
|
||||
create_path(fix_path('outdir_img2img_samples'))
|
||||
create_path(fix_path('outdir_control_samples'))
|
||||
create_path(fix_path('outdir_extras_samples'))
|
||||
create_path(fix_path('outdir_init_images'))
|
||||
create_path(fix_path('outdir_save'))
|
||||
create_path(fix_path('outdir_video'))
|
||||
if not base_grids:
|
||||
create_path(fix_path('outdir_txt2img_grids'))
|
||||
create_path(fix_path('outdir_img2img_grids'))
|
||||
create_path(fix_path('outdir_control_grids'))
|
||||
create_path(fix_path('styles_dir'))
|
||||
create_path(fix_path('yolo_dir'))
|
||||
create_path(fix_path('wildcards_dir'))
|
||||
create_path(fix_path('autocomplete_dir'))
|
||||
|
||||
# Create resolved output paths (base + specific)
|
||||
# per-type output dirs resolve against data_path via fix_path(), so create them bare
|
||||
# only when no base folder is set; with a base configured, the resolved base+specific
|
||||
# paths below are the real targets and the bare versions would just litter data_path
|
||||
base_samples = opts.data.get('outdir_samples', '').rstrip('/\\')
|
||||
base_grids = opts.data.get('outdir_grids', '').rstrip('/\\')
|
||||
if base_samples:
|
||||
create_path(resolve_output_path(base_samples, opts.data.get('outdir_txt2img_samples', '')))
|
||||
create_path(resolve_output_path(base_samples, opts.data.get('outdir_img2img_samples', '')))
|
||||
@@ -154,10 +140,22 @@ def create_paths(opts):
|
||||
create_path(resolve_output_path(base_samples, opts.data.get('outdir_save', '')))
|
||||
create_path(resolve_output_path(base_samples, opts.data.get('outdir_video', '')))
|
||||
create_path(resolve_output_path(base_samples, opts.data.get('outdir_init_images', '')))
|
||||
else:
|
||||
create_path(fix_path('outdir_txt2img_samples'))
|
||||
create_path(fix_path('outdir_img2img_samples'))
|
||||
create_path(fix_path('outdir_control_samples'))
|
||||
create_path(fix_path('outdir_extras_samples'))
|
||||
create_path(fix_path('outdir_init_images'))
|
||||
create_path(fix_path('outdir_save'))
|
||||
create_path(fix_path('outdir_video'))
|
||||
if base_grids:
|
||||
create_path(resolve_output_path(base_grids, opts.data.get('outdir_txt2img_grids', '')))
|
||||
create_path(resolve_output_path(base_grids, opts.data.get('outdir_img2img_grids', '')))
|
||||
create_path(resolve_output_path(base_grids, opts.data.get('outdir_control_grids', '')))
|
||||
else:
|
||||
create_path(fix_path('outdir_txt2img_grids'))
|
||||
create_path(fix_path('outdir_img2img_grids'))
|
||||
create_path(fix_path('outdir_control_grids'))
|
||||
|
||||
|
||||
class Prioritize:
|
||||
|
||||
@@ -476,9 +476,9 @@ def create_settings(cmd_opts):
|
||||
|
||||
# --- System Paths ---
|
||||
options_templates.update(options_section(('system-paths', "System Paths"), {
|
||||
"models_paths_sep_options": OptionInfo("<h2>Models Paths</h2>", "", gr.HTML),
|
||||
"models_paths_sep_options": OptionInfo("<h2>Models root folder</h2>", "", gr.HTML),
|
||||
"models_dir": OptionInfo('models', "Root model folder", folder=True),
|
||||
"model_paths_sep_options": OptionInfo("<h2>Paths for specific models</h2>", "", gr.HTML),
|
||||
"model_paths_sep_options": OptionInfo("<h2>Folders for specific models</h2>", "", gr.HTML),
|
||||
"ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models", folder=True),
|
||||
"diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Huggingface models", folder=True),
|
||||
"hfcache_dir": OptionInfo(default_hfcache_dir, "Folder for Huggingface cache", folder=True),
|
||||
@@ -500,6 +500,10 @@ def create_settings(cmd_opts):
|
||||
"scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'SCUNet'), "Folder with SCUNet models", folder=True),
|
||||
"swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Folder with SwinIR models", folder=True),
|
||||
"clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models", folder=True),
|
||||
|
||||
"allowed_paths_sep_options": OptionInfo("<h2>Allowed folders</h2>", "", gr.HTML),
|
||||
"allowed_paths": OptionInfo('', "Additional folders with access permissions", folder=True),
|
||||
|
||||
"other_paths_sep_options": OptionInfo("<h2>Cache folders</h2>", "", gr.HTML),
|
||||
"clean_temp_dir_at_start": OptionInfo(True, "Cleanup temporary folder on startup"),
|
||||
"temp_dir": OptionInfo("", "Directory for temporary files; leave empty for default", folder=True),
|
||||
|
||||
@@ -1181,7 +1181,7 @@
|
||||
{"id": "video_params_generic", "label": "Models", "localized": "", "hint": "Download, convert or merge your models and manage models metadata", "ui": "video"},
|
||||
{"id": "", "label": "Models & Networks", "localized": "", "hint": "View lists of all available models and networks"},
|
||||
{"id": "", "label": "Models List", "localized": "", "hint": "Table listing all discovered local model checkpoints, paths, and architectures"},
|
||||
{"id": "", "label": "Models Paths", "localized": "", "hint": "Configure directory locations for models, networks, and upscalers", "ui": "settings_system-paths"},
|
||||
{"id": "", "label": "Models root folder", "localized": "", "hint": "Configure directory locations for models, networks, and upscalers", "ui": "settings_system-paths"},
|
||||
{"id": "", "label": "Modern", "localized": "", "hint": "ModernUI responsive theme with dynamic pane layouts", "ui": "settings_ui"},
|
||||
{"id": "", "label": "ModernUI", "localized": "", "hint": "Modern responsive user interface theme for SD.Next"},
|
||||
{"id": "", "label": "Modular Pipelines", "localized": "", "hint": "Modular Diffusers pipeline architecture settings", "ui": "settings_model_options"},
|
||||
|
||||
@@ -323,6 +323,14 @@ def mount_subpath(app):
|
||||
log.info(f'Mounted: subpath="{shared.opts.subpath}"')
|
||||
|
||||
|
||||
def resolve_paths(allowed_paths):
|
||||
folders = [modules.paths.resolve_output_path(f) for f in allowed_paths]
|
||||
folders = [f for f in folders if os.path.isdir(f)]
|
||||
folders = sorted(set(folders)) # remove duplicates
|
||||
log.debug(f'Paths: {folders}')
|
||||
return folders
|
||||
|
||||
|
||||
def start_server(
|
||||
blocks,
|
||||
server_name: str | None = None, # pylint: disable=redefined-outer-name
|
||||
@@ -357,6 +365,23 @@ def start_server(
|
||||
path_to_local_server = f"http://{url_host_name}:{server_port}/"
|
||||
return server_name, server_port, path_to_local_server, app, server
|
||||
|
||||
def get_paths():
|
||||
allowed_paths = [os.path.dirname(__file__)]
|
||||
if shared.cmd_opts.data_dir is not None and os.path.isdir(shared.cmd_opts.data_dir):
|
||||
allowed_paths.append(shared.cmd_opts.data_dir)
|
||||
if shared.cmd_opts.models_dir is not None and os.path.isdir(shared.cmd_opts.models_dir):
|
||||
allowed_paths.append(shared.cmd_opts.models_dir)
|
||||
if shared.cmd_opts.allowed_paths is not None:
|
||||
allowed_paths += [p for p in shared.cmd_opts.allowed_paths if os.path.isdir(p)]
|
||||
if os.environ.get('SD_PATH_DEBUG', None):
|
||||
log.trace(f'Path allowed: why=args allowed={shared.demo.allowed_paths}')
|
||||
if shared.opts.allowed_paths:
|
||||
folders = [f.strip() for f in shared.opts.allowed_paths.split(',') if f.strip()]
|
||||
allowed_paths = [f for f in folders if os.path.isdir(f)]
|
||||
if os.environ.get('SD_PATH_DEBUG', None):
|
||||
log.trace(f'Path allowed: why=opts allowed={shared.demo.allowed_paths}')
|
||||
return allowed_paths
|
||||
|
||||
|
||||
def start_ui():
|
||||
log.debug('UI start sequence')
|
||||
@@ -396,14 +421,8 @@ def start_ui():
|
||||
|
||||
global local_url # pylint: disable=global-statement
|
||||
stdout = io.StringIO()
|
||||
allowed_paths = [os.path.dirname(__file__)]
|
||||
if shared.cmd_opts.data_dir is not None and os.path.isdir(shared.cmd_opts.data_dir):
|
||||
allowed_paths.append(shared.cmd_opts.data_dir)
|
||||
if shared.cmd_opts.models_dir is not None and os.path.isdir(shared.cmd_opts.models_dir):
|
||||
allowed_paths.append(shared.cmd_opts.models_dir)
|
||||
if shared.cmd_opts.allowed_paths is not None:
|
||||
allowed_paths += [p for p in shared.cmd_opts.allowed_paths if os.path.isdir(p)]
|
||||
log.info(f'Server: name={server_name} port={shared.cmd_opts.port} paths={allowed_paths} ssl={shared.cmd_opts.tls_keyfile}:{shared.cmd_opts.tls_certfile} auth={len(auth_pairs)}')
|
||||
|
||||
log.info(f'Server: name={server_name} port={shared.cmd_opts.port} ssl={shared.cmd_opts.tls_keyfile}:{shared.cmd_opts.tls_certfile} auth={len(auth_pairs)}')
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
import gradio.networking
|
||||
gradio.networking.start_server = start_server
|
||||
@@ -421,7 +440,7 @@ def start_ui():
|
||||
show_api=False,
|
||||
quiet=True,
|
||||
favicon_path='ui/assets/favicon.svg',
|
||||
allowed_paths=allowed_paths,
|
||||
allowed_paths=get_paths(),
|
||||
app_kwargs=fastapi_args,
|
||||
_frontend=True and shared.cmd_opts.share,
|
||||
)
|
||||
@@ -490,6 +509,7 @@ def webui(restart=False, _exit=False, profiler=None):
|
||||
|
||||
load_model()
|
||||
mount_subpath(app)
|
||||
shared.demo.allowed_paths = resolve_paths(shared.demo.allowed_paths)
|
||||
shared.opts.save()
|
||||
|
||||
if shared.cmd_opts.profile:
|
||||
|
||||
Reference in New Issue
Block a user