Enhance Settings.fds management and installation flag handling

- Introduced `comfy_on_disk` and `sync_is_installed` functions to manage the `IsInstalled` flag based on the presence of ComfyUI, ensuring accurate installation state reporting.
- Updated `install_swarm_comfy.py` to clear the `IsInstalled` flag when necessary, allowing the installation process to proceed without conflicts.
- Improved logic to prevent setting `IsInstalled` to true on first boot or empty data volumes, addressing issues with the InstallConfirmWS process.
- Enhanced diagnostics and logging for installation state changes, providing clearer feedback during the installation process.
This commit is contained in:
Leonid Pershin
2026-08-21 12:47:25 +03:00
parent 57d38bd9f6
commit d785f47534
2 changed files with 171 additions and 31 deletions
+115 -23
View File
@@ -287,41 +287,85 @@ import sys
import time import time
from pathlib import Path from pathlib import Path
def comfy_on_disk() -> bool:
"""True only after first Comfy install — not on empty data volume."""
override = (os.environ.get("GPU_RENT_COMFY_PRESENT") or "").strip().lower()
if override in ("1", "true", "yes"):
return True
if override in ("0", "false", "no"):
return False
for cand in (
Path("/mnt/swarm_data/dlbackend/ComfyUI/venv/bin/python"),
Path("/mnt/swarm_data/dlbackend/ComfyUI/main.py"),
Path("/opt/swarmui/dlbackend/ComfyUI/venv/bin/python"),
Path("/opt/swarmui/dlbackend/ComfyUI/main.py"),
):
if cand.is_file():
return True
backends = Path("/mnt/swarm_data/Data/Backends.fds")
try:
if backends.is_file() and "StartScript" in backends.read_text(
encoding="utf-8", errors="replace"
):
return True
except OSError:
pass
return False
def sync_is_installed(text: str) -> tuple[str, str | None]:
"""Set IsInstalled true only when Comfy exists; else clear leftover true."""
if comfy_on_disk():
if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text):
return text, None
installed_block = (
"IsInstalled: true\n"
f"InstallDate: {time.strftime('%Y-%m-%d')}\n"
"InstallVersion: gpu-rent\n"
)
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
return text, "patched IsInstalled: true (Comfy on disk)"
if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text):
text = re.sub(r"(?im)^(\s*IsInstalled:\s*).*$", r"\1false", text, count=1)
return text, "cleared IsInstalled: false (нет Comfy — первый InstallConfirmWS)"
return text, None
p = Path(os.environ.get("GPU_RENT_SETTINGS_FDS") or "/mnt/swarm_data/Data/Settings.fds") p = Path(os.environ.get("GPU_RENT_SETTINGS_FDS") or "/mnt/swarm_data/Data/Settings.fds")
fname = (os.environ.get("GPU_RENT_AUTOCOMPLETE_FILE") or "").strip() fname = (os.environ.get("GPU_RENT_AUTOCOMPLETE_FILE") or "").strip()
if not fname: if not fname:
print("no GPU_RENT_AUTOCOMPLETE_FILE", file=sys.stderr) print("no GPU_RENT_AUTOCOMPLETE_FILE", file=sys.stderr)
raise SystemExit(1) raise SystemExit(1)
installed_block = ( ac_block = (
"DefaultUser:\n"
" AutoComplete:\n"
f" Source: {fname}\n"
" EscapeParens: true\n"
)
installed_prefix = (
"IsInstalled: true\n" "IsInstalled: true\n"
f"InstallDate: {time.strftime('%Y-%m-%d')}\n" f"InstallDate: {time.strftime('%Y-%m-%d')}\n"
"InstallVersion: gpu-rent\n" "InstallVersion: gpu-rent\n"
) )
block = (
installed_block
+ "DefaultUser:\n"
" AutoComplete:\n"
f" Source: {fname}\n"
" EscapeParens: true\n"
)
if not p.is_file(): if not p.is_file():
p.parent.mkdir(parents=True, exist_ok=True) p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(block, encoding="utf-8") prefix = installed_prefix if comfy_on_disk() else ""
print(f"created Settings.fds IsInstalled+AutoComplete.Source={fname}") p.write_text(prefix + ac_block, encoding="utf-8")
extra = "+IsInstalled" if prefix else "без IsInstalled (первый Comfy install)"
print(f"created Settings.fds AutoComplete.Source={fname} {extra}")
raise SystemExit(0) raise SystemExit(0)
text = p.read_text(encoding="utf-8", errors="replace") text = p.read_text(encoding="utf-8", errors="replace")
# Never leave a Settings.fds that sends the UI to /Install. text, note = sync_is_installed(text)
if not re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text): if note:
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") p.write_text(text, encoding="utf-8")
print("patched IsInstalled: true (was missing/false)") print(note)
text = p.read_text(encoding="utf-8", errors="replace") text = p.read_text(encoding="utf-8", errors="replace")
if re.search(rf"^\s*Source:\s*{re.escape(fname)}\s*$", text, re.M): if re.search(rf"^\s*Source:\s*{re.escape(fname)}\s*$", text, re.M):
@@ -356,14 +400,19 @@ if re.search(r"^DefaultUser:\s*$", text, re.M):
p.write_text(new, encoding="utf-8") p.write_text(new, encoding="utf-8")
print(f"inserted AutoComplete under DefaultUser Source={fname}") print(f"inserted AutoComplete under DefaultUser Source={fname}")
else: else:
p.write_text(text.rstrip() + "\n\n" + block, encoding="utf-8") p.write_text(text.rstrip() + "\n\n" + ac_block, encoding="utf-8")
print(f"appended DefaultUser.AutoComplete Source={fname}") print(f"appended DefaultUser.AutoComplete Source={fname}")
''' '''
_ENSURE_INSTALLED_PY = r''' _ENSURE_INSTALLED_PY = r'''
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Ensure Settings.fds has IsInstalled: true so UI skips /Install wizard.""" """Sync Settings.fds IsInstalled with whether Comfy actually exists.
Warm re-up: backends/venv present → IsInstalled true (UI skips /Install).
First boot / empty dlbackend: do NOT set true — InstallConfirmWS refuses
with "Server is already installed!" and never clones ComfyUI.
"""
from __future__ import annotations from __future__ import annotations
import os import os
@@ -372,6 +421,31 @@ import sys
import time import time
from pathlib import Path from pathlib import Path
def comfy_on_disk() -> bool:
override = (os.environ.get("GPU_RENT_COMFY_PRESENT") or "").strip().lower()
if override in ("1", "true", "yes"):
return True
if override in ("0", "false", "no"):
return False
Path("/mnt/swarm_data/dlbackend/ComfyUI/venv/bin/python"),
Path("/mnt/swarm_data/dlbackend/ComfyUI/main.py"),
Path("/opt/swarmui/dlbackend/ComfyUI/venv/bin/python"),
Path("/opt/swarmui/dlbackend/ComfyUI/main.py"),
):
if cand.is_file():
return True
backends = Path("/mnt/swarm_data/Data/Backends.fds")
try:
if backends.is_file() and "StartScript" in backends.read_text(
encoding="utf-8", errors="replace"
):
return True
except OSError:
pass
return False
paths = [] paths = []
env = (os.environ.get("GPU_RENT_SETTINGS_FDS") or "").strip() env = (os.environ.get("GPU_RENT_SETTINGS_FDS") or "").strip()
if env: if env:
@@ -392,6 +466,7 @@ for p in paths:
seen.add(key) seen.add(key)
uniq.append(p) uniq.append(p)
present = comfy_on_disk()
changed = False changed = False
for p in uniq: for p in uniq:
try: try:
@@ -408,7 +483,10 @@ for p in uniq:
if same and changed: if same and changed:
continue continue
if not p.is_file(): if not p.is_file():
# Only create on data volume path # Don't invent IsInstalled=true on empty first boot.
if not present:
print(f"skip create {p}: Comfy ещё нет")
continue
if "/mnt/swarm_data/" not in str(p): if "/mnt/swarm_data/" not in str(p):
continue continue
p.parent.mkdir(parents=True, exist_ok=True) p.parent.mkdir(parents=True, exist_ok=True)
@@ -424,6 +502,17 @@ for p in uniq:
changed = True changed = True
continue continue
text = p.read_text(encoding="utf-8", errors="replace") text = p.read_text(encoding="utf-8", errors="replace")
if not present:
if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text):
text = re.sub(
r"(?im)^(\s*IsInstalled:\s*).*$", r"\1false", text, count=1
)
p.write_text(text, encoding="utf-8")
print(f"cleared {p}: IsInstalled false (нет Comfy — первый install)")
changed = True
else:
print(f"ok {p}: IsInstalled not true, Comfy отсутствует")
continue
if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text): if re.search(r"(?im)^\s*IsInstalled:\s*true\s*$", text):
print(f"ok {p}: IsInstalled true") print(f"ok {p}: IsInstalled true")
continue continue
@@ -475,7 +564,10 @@ def _merge_autocomplete_into_settings(
def ensure_settings_is_installed(cfg: Config, host: str, log: Log) -> bool: def ensure_settings_is_installed(cfg: Config, host: str, log: Log) -> bool:
"""Make sure Settings.fds has IsInstalled:true (UI /Install wizard). """Sync Settings.fds IsInstalled with Comfy on disk.
Warm: backends/venv exist → true (skip UI /Install). First boot / empty
dlbackend → leave false/missing so InstallConfirmWS can clone Comfy.
Returns True if the file was created/patched (caller may need SwarmUI restart). Returns True if the file was created/patched (caller may need SwarmUI restart).
""" """
@@ -936,7 +1028,7 @@ def provision_vm(
) )
if cfg.pull_output: if cfg.pull_output:
pull_tree(cfg, host, f"{DATA}/Output", cfg.local_output_dir, log) pull_tree(cfg, host, f"{DATA}/Output", cfg.local_output_dir, log)
# Avoid /Install wizard when backends already exist but Settings lack flag. # Warm: IsInstalled true if Comfy exists. First boot: keep false.
if ensure_settings_is_installed(cfg, host, log): if ensure_settings_is_installed(cfg, host, log):
restart = True restart = True
else: else:
+56 -8
View File
@@ -2,7 +2,11 @@
"""Headless SwarmUI first-install: ComfyUI backend via InstallConfirmWS. """Headless SwarmUI first-install: ComfyUI backend via InstallConfirmWS.
Runs on the VM (stdlib only). Idempotent: skips when backends exist or Runs on the VM (stdlib only). Idempotent: skips when backends exist or
dlbackend/ComfyUI venv is already present and IsInstalled. dlbackend/ComfyUI venv is already present.
InstallConfirmWS refuses with ``Server is already installed!`` if
Settings.fds has IsInstalled:true — even when Comfy was never cloned.
First boot and leftover flags must clear that bit before the WS call.
""" """
from __future__ import annotations from __future__ import annotations
@@ -778,6 +782,35 @@ def settings_is_installed() -> bool | None:
return None return None
def clear_settings_installed_flag() -> bool:
"""Set IsInstalled:false so InstallConfirmWS is allowed to run."""
if not SETTINGS.is_file():
return False
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*false\s*$", text):
return False
if re.search(r"(?im)^\s*IsInstalled:\s*", text):
text = re.sub(r"(?im)^(\s*IsInstalled:\s*).*$", r"\1false", text, count=1)
else:
text = "IsInstalled: false\n" + text
SETTINGS.write_text(text, encoding="utf-8")
print(
"patched Settings.fds IsInstalled=false (InstallConfirmWS)",
flush=True,
)
return True
def prepare_first_install() -> None:
"""Clear leftover IsInstalled and reload Swarm so the installer WS works."""
if clear_settings_installed_flag():
restart_swarmui_local()
def ensure_settings_installed_flag() -> bool: def ensure_settings_installed_flag() -> bool:
"""Write IsInstalled:true if missing — UI otherwise stays on /Install.""" """Write IsInstalled:true if missing — UI otherwise stays on /Install."""
SETTINGS.parent.mkdir(parents=True, exist_ok=True) SETTINGS.parent.mkdir(parents=True, exist_ok=True)
@@ -1086,14 +1119,16 @@ def main() -> int:
return 0 return 0
installed = settings_is_installed() installed = settings_is_installed()
if installed is True and not comfy_venv_ok() and bstat == "empty": # Leftover IsInstalled=true (autocomplete/warm patch, failed previous up)
# makes InstallConfirmWS return "Server is already installed!" and skip clone.
if installed is True and not comfy_venv_ok() and need_recover:
print( print(
"WARN: Settings IsInstalled=true but backends empty and no Comfy venv. " "IsInstalled=true, backends empty, нет Comfy venv "
"Open SwarmUI → Server → Backends and add ComfyUI Self-Starting, " "сбрасываю флаг и ставлю Comfy через InstallConfirmWS",
"or delete Data/Settings.fds IsInstalled and re-run up." flush=True,
) )
run_diagnostics() prepare_first_install()
return 1 installed = settings_is_installed()
if ( if (
installed is True installed is True
@@ -1139,7 +1174,20 @@ def main() -> int:
pass pass
try: try:
sid = get_session(time.time() + 120) sid = get_session(time.time() + 120)
run_install(sid) try:
run_install(sid)
except SystemExit as exc:
msg = str(exc).lower()
if "already installed" not in msg:
raise
print(
"InstallConfirmWS: Server is already installed — "
"сброс IsInstalled и повтор",
flush=True,
)
prepare_first_install()
sid = get_session(time.time() + 120)
run_install(sid)
finally: finally:
try: try:
marker.unlink(missing_ok=True) marker.unlink(missing_ok=True)