Enhance CLI options and idle-killer functionality

- Added a `--update` option to the `up` command in the CLI for forced updates during warm ACTIVE states.
- Improved the idle-killer logic with new functions to check for reusable credentials and refresh settings on existing remote credentials.
- Enhanced the `seed_civitai` function to implement caching for model jobs, optimizing the provisioning process.
- Updated the installation script for Ollama to prevent unnecessary restarts when the service configuration has not changed.
- Added diagnostics for model job caching and improved error handling in various functions.
This commit is contained in:
Leonid Pershin
2026-08-21 11:22:14 +03:00
parent d3d689052d
commit 241ada62cb
7 changed files with 300 additions and 9 deletions
+108 -7
View File
@@ -38,6 +38,18 @@ DATA = "/mnt/swarm_data"
_OLLAMA_INSTALL_ENV = ("OLLAMA_VERSION", "OLLAMA_SHA256")
def _models_manifest_fp(cfg: Config) -> str:
"""Stable fingerprint of models.yaml so warm seed can skip API+fetch."""
import hashlib
path = Path(cfg.models_manifest)
try:
raw = path.read_bytes()
except OSError:
raw = b""
return hashlib.sha256(raw).hexdigest()[:16]
def _remote_llm_env(cfg: Config, *keys: str) -> dict[str, str]:
import os
@@ -408,6 +420,41 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
log("model-seed пропущен: манифест пуст — дефолт SwarmUI")
return
# Warm short-circuit: last successful jobs list still present on disk.
cache_remote = f"{DATA}/.gpu-rent-models-jobs.json"
try:
cached_raw = run_ssh(
cfg,
host,
f"test -f {cache_remote} && cat {cache_remote} || true",
check=False,
timeout=20,
).strip()
except Exception:
cached_raw = ""
if cached_raw.startswith("{"):
try:
cached = json.loads(cached_raw)
dests = [str(d) for d in (cached.get("dests") or []) if d]
fp = str(cached.get("fp") or "")
want_fp = _models_manifest_fp(cfg)
if dests and fp == want_fp:
check = " && ".join(f"test -f {shlex.quote(d)}" for d in dests)
ok = run_ssh(
cfg,
host,
f"if {check}; then echo ALL_OK; else echo MISSING; fi",
check=False,
timeout=30,
).strip()
if "ALL_OK" in ok:
log(
f"model-seed: skip — {len(dests)} файл(ов) уже на диске (cache)"
)
return
except (json.JSONDecodeError, TypeError, CloudError):
pass
jobs: list[dict] = []
for entry in entries:
url = (entry.url or "").strip()
@@ -514,6 +561,31 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
if not any(e.kind == "checkpoint" for e in entries):
log("в манифесте нет checkpoint — генерация может не стартовать")
dests = [str(j.get("dest") or "") for j in jobs if j.get("dest")]
if dests:
check = " && ".join(f"test -f {shlex.quote(d)}" for d in dests)
present = run_ssh(
cfg,
host,
f"if {check}; then echo ALL_OK; else echo MISSING; fi",
check=False,
timeout=30,
).strip()
if "ALL_OK" in present:
put_text(
cfg,
host,
cache_remote,
json.dumps({"fp": _models_manifest_fp(cfg), "dests": dests}, indent=2)
+ "\n",
)
log(
f"model-seed: skip fetch — {len(dests)} файл(ов) уже на диске "
f"(civitai={civ_n}, huggingface={hf_n})"
)
return
put_text(cfg, host, "/tmp/gpu-rent-civitai-jobs.json", json.dumps(jobs, indent=2))
if cfg.civitai_api_token:
put_text(
@@ -533,6 +605,12 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
timeout=7200,
log=log,
)
put_text(
cfg,
host,
cache_remote,
json.dumps({"fp": _models_manifest_fp(cfg), "dests": dests}, indent=2) + "\n",
)
def ensure_swarmui_running(cfg: Config, host: str, log: Log, restart: bool) -> None:
@@ -594,16 +672,39 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
if not names:
log("ollama-models.yaml пуст — pull skip")
else:
put_text(cfg, host, "/tmp/gpu-rent-ollama-models.json", json.dumps(names, indent=2))
log(f"Ollama: pull {len(names)} из манифеста")
run_python(
# Fast path: all tags already present — skip upload/pull script.
listed = run_ssh(
cfg,
host,
_pkg_text("ollama_pull.py"),
remote_path="/tmp/gpu-rent-ollama_pull.py",
timeout=7200,
log=log,
"ollama list 2>/dev/null | awk 'NR>1 {print $1}' || true",
check=False,
timeout=30,
)
have = {ln.strip() for ln in listed.splitlines() if ln.strip()}
missing = []
for name in names:
if name in have or (
":" not in name and f"{name}:latest" in have
) or (
name.endswith(":latest") and name.rsplit(":", 1)[0] in have
):
continue
missing.append(name)
if not missing:
log(f"ollama pull: skip — все {len(names)} уже есть")
else:
put_text(
cfg, host, "/tmp/gpu-rent-ollama-models.json", json.dumps(missing, indent=2)
)
log(f"Ollama: pull {len(missing)} из манифеста (нет: {len(missing)})")
run_python(
cfg,
host,
_pkg_text("ollama_pull.py"),
remote_path="/tmp/gpu-rent-ollama_pull.py",
timeout=7200,
log=log,
)
else:
raise CloudError(f"неизвестный LLM_RUNTIME={runtime!r}")
st = load_state()