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.
This commit is contained in:
+140
-2
@@ -284,6 +284,7 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
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")
|
||||||
@@ -292,8 +293,15 @@ 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 = (
|
||||||
|
"IsInstalled: true\n"
|
||||||
|
f"InstallDate: {time.strftime('%Y-%m-%d')}\n"
|
||||||
|
"InstallVersion: gpu-rent\n"
|
||||||
|
)
|
||||||
|
|
||||||
block = (
|
block = (
|
||||||
"DefaultUser:\n"
|
installed_block
|
||||||
|
+ "DefaultUser:\n"
|
||||||
" AutoComplete:\n"
|
" AutoComplete:\n"
|
||||||
f" Source: {fname}\n"
|
f" Source: {fname}\n"
|
||||||
" EscapeParens: true\n"
|
" EscapeParens: true\n"
|
||||||
@@ -302,10 +310,20 @@ block = (
|
|||||||
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")
|
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)
|
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.
|
||||||
|
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):
|
if re.search(rf"^\s*Source:\s*{re.escape(fname)}\s*$", text, re.M):
|
||||||
print(f"AutoComplete.Source already {fname}")
|
print(f"AutoComplete.Source already {fname}")
|
||||||
raise SystemExit(0)
|
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(
|
def _merge_autocomplete_into_settings(
|
||||||
cfg: Config, host: str, settings_path: str, filename: str, log: Log
|
cfg: Config, host: str, settings_path: str, filename: str, log: Log
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -364,6 +474,31 @@ def _merge_autocomplete_into_settings(
|
|||||||
log(line.strip())
|
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:
|
def _download_url(host: str, version_id: int, file_info: dict) -> str:
|
||||||
raw = str(file_info.get("downloadUrl") or "")
|
raw = str(file_info.get("downloadUrl") or "")
|
||||||
if "civitai." in raw and "/api/download/" in raw:
|
if "civitai." in raw and "/api/download/" in raw:
|
||||||
@@ -801,6 +936,9 @@ 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.
|
||||||
|
if ensure_settings_is_installed(cfg, host, log):
|
||||||
|
restart = True
|
||||||
else:
|
else:
|
||||||
log("SwarmUI: skip (llm-only)")
|
log("SwarmUI: skip (llm-only)")
|
||||||
run_ssh(
|
run_ssh(
|
||||||
|
|||||||
@@ -641,6 +641,42 @@ def settings_is_installed() -> bool | None:
|
|||||||
return 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:
|
def comfy_venv_ok() -> bool:
|
||||||
return COMFY_VENV.is_file() and os.access(COMFY_VENV, os.X_OK)
|
return COMFY_VENV.is_file() and os.access(COMFY_VENV, os.X_OK)
|
||||||
|
|
||||||
@@ -889,17 +925,26 @@ def main() -> int:
|
|||||||
run_diagnostics()
|
run_diagnostics()
|
||||||
return 1
|
return 1
|
||||||
if bstat in ("running", "idle", "loading", "some_loading") and comfy_venv_ok():
|
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")
|
print(f"recovered to {bstat} — skip InstallConfirmWS")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Backends already registered (healthy / loading / suspended) + venv → skip.
|
# Backends already registered (healthy / loading / suspended) + venv → skip.
|
||||||
if bstat == "idle":
|
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")
|
print("backends present (idle/suspended) + skip install")
|
||||||
return 0
|
return 0
|
||||||
# disabled = empty StartScript / not usable — must recover, not skip.
|
# disabled = empty StartScript / not usable — must recover, not skip.
|
||||||
need_recover = bstat in ("empty", "disabled", "all_disabled", "unknown")
|
need_recover = bstat in ("empty", "disabled", "all_disabled", "unknown")
|
||||||
if not need_recover and bstat != "errored" and not bstat.startswith("error:"):
|
if not need_recover and bstat != "errored" and not bstat.startswith("error:"):
|
||||||
if comfy_venv_ok():
|
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")
|
print(f"backends present ({bstat}) + venv — skip install")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -94,3 +94,22 @@ def test_cli_has_update_flag():
|
|||||||
src = Path(cli.__file__).read_text(encoding="utf-8")
|
src = Path(cli.__file__).read_text(encoding="utf-8")
|
||||||
assert '"--update"' in src
|
assert '"--update"' in src
|
||||||
assert "force_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
|
||||||
|
|||||||
Reference in New Issue
Block a user