From fc60fef2798251304f70e6138bf44d2cd3e52f18 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Fri, 21 Aug 2026 11:27:06 +0300 Subject: [PATCH] Enhance Settings.fds management and installation checks - Introduced functions to ensure the `IsInstalled` flag is set in `Settings.fds`, preventing the UI from displaying the /Install wizard. - Added logic to create or patch `Settings.fds` with installation details, including the installation date and version. - Updated the `install_swarm_comfy.py` script to call the new function, ensuring the installation state is correctly managed during backend operations. - Added tests to verify the presence of the `IsInstalled` flag in the relevant scripts and ensure proper functionality during installation checks. --- src/gpu_rent/provision.py | 142 ++++++++++++++++++++- src/gpu_rent/remote/install_swarm_comfy.py | 45 +++++++ tests/test_warm_up_skips.py | 19 +++ 3 files changed, 204 insertions(+), 2 deletions(-) diff --git a/src/gpu_rent/provision.py b/src/gpu_rent/provision.py index c512bd6..c758907 100644 --- a/src/gpu_rent/provision.py +++ b/src/gpu_rent/provision.py @@ -284,6 +284,7 @@ from __future__ import annotations import os import re import sys +import time from pathlib import Path p = Path(os.environ.get("GPU_RENT_SETTINGS_FDS") or "/mnt/swarm_data/Data/Settings.fds") @@ -292,8 +293,15 @@ if not fname: print("no GPU_RENT_AUTOCOMPLETE_FILE", file=sys.stderr) raise SystemExit(1) +installed_block = ( + "IsInstalled: true\n" + f"InstallDate: {time.strftime('%Y-%m-%d')}\n" + "InstallVersion: gpu-rent\n" +) + block = ( - "DefaultUser:\n" + installed_block + + "DefaultUser:\n" " AutoComplete:\n" f" Source: {fname}\n" " EscapeParens: true\n" @@ -302,10 +310,20 @@ block = ( if not p.is_file(): p.parent.mkdir(parents=True, exist_ok=True) p.write_text(block, encoding="utf-8") - print(f"created Settings.fds AutoComplete.Source={fname}") + print(f"created Settings.fds IsInstalled+AutoComplete.Source={fname}") raise SystemExit(0) text = p.read_text(encoding="utf-8", errors="replace") +# Never leave a Settings.fds that sends the UI to /Install. +if not re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text): + if re.search(r"(?im)^\s*IsInstalled:\s*", text): + text = re.sub(r"(?im)^(\s*IsInstalled:\s*).*$", r"\1true", text, count=1) + else: + text = installed_block + text + p.write_text(text, encoding="utf-8") + print("patched IsInstalled: true (was missing/false)") + text = p.read_text(encoding="utf-8", errors="replace") + if re.search(rf"^\s*Source:\s*{re.escape(fname)}\s*$", text, re.M): print(f"AutoComplete.Source already {fname}") raise SystemExit(0) @@ -343,6 +361,98 @@ else: ''' +_ENSURE_INSTALLED_PY = r''' +#!/usr/bin/env python3 +"""Ensure Settings.fds has IsInstalled: true so UI skips /Install wizard.""" +from __future__ import annotations + +import os +import re +import sys +import time +from pathlib import Path + +paths = [] +env = (os.environ.get("GPU_RENT_SETTINGS_FDS") or "").strip() +if env: + paths.append(Path(env)) +paths.extend( + [ + Path("/mnt/swarm_data/Data/Settings.fds"), + Path("/opt/swarmui/Data/Settings.fds"), + ] +) +# Unique while preserving order +seen = set() +uniq: list[Path] = [] +for p in paths: + key = str(p) + if key in seen: + continue + seen.add(key) + uniq.append(p) + +changed = False +for p in uniq: + try: + same = False + if p.is_file(): + for q in uniq: + if q is not p and q.is_file(): + try: + if p.resolve() == q.resolve(): + same = True + break + except OSError: + pass + if same and changed: + continue + if not p.is_file(): + # Only create on data volume path + if "/mnt/swarm_data/" not in str(p): + continue + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text( + "IsInstalled: true\n" + f"InstallDate: {time.strftime('%Y-%m-%d')}\n" + "InstallVersion: gpu-rent\n" + "DefaultUser:\n" + " Theme: modern_dark\n", + encoding="utf-8", + ) + print(f"created {p} with IsInstalled") + changed = True + continue + text = p.read_text(encoding="utf-8", errors="replace") + if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text): + print(f"ok {p}: IsInstalled true") + continue + if re.search(r"(?im)^\s*IsInstalled:\s*", text): + text = re.sub(r"(?im)^(\s*IsInstalled:\s*).*$", r"\1true", text, count=1) + else: + text = ( + "IsInstalled: true\n" + f"InstallDate: {time.strftime('%Y-%m-%d')}\n" + "InstallVersion: gpu-rent\n" + + text + ) + if not re.search(r"(?im)^\s*InstallDate:\s*", text): + text = re.sub( + r"(?im)^(\s*IsInstalled:\s*true\s*\n)", + rf"\1InstallDate: {time.strftime('%Y-%m-%d')}\n", + text, + count=1, + ) + p.write_text(text, encoding="utf-8") + print(f"patched {p}: IsInstalled true") + changed = True + except OSError as exc: + print(f"skip {p}: {exc}") + +print("CHANGED" if changed else "NOOP") +''' + + def _merge_autocomplete_into_settings( cfg: Config, host: str, settings_path: str, filename: str, log: Log ) -> None: @@ -364,6 +474,31 @@ def _merge_autocomplete_into_settings( log(line.strip()) +def ensure_settings_is_installed(cfg: Config, host: str, log: Log) -> bool: + """Make sure Settings.fds has IsInstalled:true (UI /Install wizard). + + Returns True if the file was created/patched (caller may need SwarmUI restart). + """ + out = run_python( + cfg, + host, + _ENSURE_INSTALLED_PY, + remote_path="/tmp/gpu-rent-ensure_installed.py", + timeout=60, + log=None, + env={"GPU_RENT_SETTINGS_FDS": f"{DATA}/Data/Settings.fds"}, + ) + changed = False + for line in (out or "").splitlines(): + line = line.strip() + if not line: + continue + log(f"Settings: {line}") + if line == "CHANGED": + changed = True + return changed + + def _download_url(host: str, version_id: int, file_info: dict) -> str: raw = str(file_info.get("downloadUrl") or "") if "civitai." in raw and "/api/download/" in raw: @@ -801,6 +936,9 @@ def provision_vm( ) if cfg.pull_output: pull_tree(cfg, host, f"{DATA}/Output", cfg.local_output_dir, log) + # Avoid /Install wizard when backends already exist but Settings lack flag. + if ensure_settings_is_installed(cfg, host, log): + restart = True else: log("SwarmUI: skip (llm-only)") run_ssh( diff --git a/src/gpu_rent/remote/install_swarm_comfy.py b/src/gpu_rent/remote/install_swarm_comfy.py index 62434c2..dc54ae9 100644 --- a/src/gpu_rent/remote/install_swarm_comfy.py +++ b/src/gpu_rent/remote/install_swarm_comfy.py @@ -641,6 +641,42 @@ def settings_is_installed() -> bool | None: return None +def ensure_settings_installed_flag() -> bool: + """Write IsInstalled:true if missing — UI otherwise stays on /Install.""" + SETTINGS.parent.mkdir(parents=True, exist_ok=True) + stamp = time.strftime("%Y-%m-%d") + if not SETTINGS.is_file(): + SETTINGS.write_text( + "IsInstalled: true\n" + f"InstallDate: {stamp}\n" + "InstallVersion: gpu-rent\n" + "DefaultUser:\n" + " Theme: modern_dark\n", + encoding="utf-8", + ) + print("created Settings.fds with IsInstalled=true", flush=True) + return True + try: + text = SETTINGS.read_text(encoding="utf-8", errors="replace") + except OSError as exc: + print(f"WARN Settings read: {exc}", flush=True) + return False + if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text): + return False + if re.search(r"(?im)^\s*IsInstalled:\s*", text): + text = re.sub(r"(?im)^(\s*IsInstalled:\s*).*$", r"\1true", text, count=1) + else: + text = ( + "IsInstalled: true\n" + f"InstallDate: {stamp}\n" + "InstallVersion: gpu-rent\n" + + text + ) + SETTINGS.write_text(text, encoding="utf-8") + print("patched Settings.fds IsInstalled=true", flush=True) + return True + + def comfy_venv_ok() -> bool: return COMFY_VENV.is_file() and os.access(COMFY_VENV, os.X_OK) @@ -889,17 +925,26 @@ def main() -> int: run_diagnostics() return 1 if bstat in ("running", "idle", "loading", "some_loading") and comfy_venv_ok(): + if ensure_settings_installed_flag(): + print("IsInstalled was missing — restart swarmui to leave /Install", flush=True) + restart_swarmui_local() print(f"recovered to {bstat} — skip InstallConfirmWS") return 0 # Backends already registered (healthy / loading / suspended) + venv → skip. if bstat == "idle": + if ensure_settings_installed_flag(): + print("IsInstalled was missing — restart swarmui to leave /Install", flush=True) + restart_swarmui_local() print("backends present (idle/suspended) + skip install") return 0 # disabled = empty StartScript / not usable — must recover, not skip. need_recover = bstat in ("empty", "disabled", "all_disabled", "unknown") if not need_recover and bstat != "errored" and not bstat.startswith("error:"): if comfy_venv_ok(): + if ensure_settings_installed_flag(): + print("IsInstalled was missing — restart swarmui to leave /Install", flush=True) + restart_swarmui_local() print(f"backends present ({bstat}) + venv — skip install") return 0 diff --git a/tests/test_warm_up_skips.py b/tests/test_warm_up_skips.py index a2cd5b9..9c041d6 100644 --- a/tests/test_warm_up_skips.py +++ b/tests/test_warm_up_skips.py @@ -94,3 +94,22 @@ def test_cli_has_update_flag(): src = Path(cli.__file__).read_text(encoding="utf-8") assert '"--update"' in src assert "force_update" in src + + +def test_autocomplete_merge_sets_is_installed(): + from gpu_rent import provision + from gpu_rent.provision import _AUTOCOMPLETE_MERGE_PY, _ENSURE_INSTALLED_PY + + assert "IsInstalled: true" in _AUTOCOMPLETE_MERGE_PY + assert "IsInstalled: true" in _ENSURE_INSTALLED_PY + assert hasattr(provision, "ensure_settings_is_installed") + + +def test_install_comfy_patches_is_installed_on_skip(): + from importlib.resources import files + + text = files("gpu_rent.remote").joinpath("install_swarm_comfy.py").read_text( + encoding="utf-8" + ) + assert "ensure_settings_installed_flag" in text + assert "leave /Install" in text