Enhance LLM and SwarmUI integration with improved configuration options
- Updated `env.example` and `gpu-rent.vars.example` to include new variables for LLM runtime and SwarmUI options. - Refactored CLI commands to support interactive selection of LLM runtime and workload type (SwarmUI, LLM, or both). - Improved access link generation to handle cases where SwarmUI is disabled, providing clearer user feedback. - Enhanced provisioning logic to conditionally bootstrap SwarmUI based on user configuration, allowing for LLM-only setups. - Updated documentation across multiple files to reflect changes in LLM integration, CLI usage, and configuration management.
This commit is contained in:
@@ -40,17 +40,19 @@ def resolve_llm_runtime(cfg: Config) -> str:
|
||||
|
||||
def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
||||
"""Build the list of user-facing endpoints (unit-tested)."""
|
||||
port = cfg.swarmui_local_port
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
links: list[AccessLink] = []
|
||||
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
||||
if tunneled:
|
||||
links.extend(
|
||||
[
|
||||
AccessLink("SwarmUI UI", base, "браузер"),
|
||||
AccessLink("SwarmUI API", f"{base}/API/", "HTTP JSON"),
|
||||
AccessLink("SwarmUI MCP", f"{base}/mcp", "Cursor mcp.json"),
|
||||
]
|
||||
)
|
||||
if swarm:
|
||||
port = cfg.swarmui_local_port
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
links.extend(
|
||||
[
|
||||
AccessLink("SwarmUI UI", base, "браузер"),
|
||||
AccessLink("SwarmUI API", f"{base}/API/", "HTTP JSON"),
|
||||
AccessLink("SwarmUI MCP", f"{base}/mcp", "Cursor mcp.json"),
|
||||
]
|
||||
)
|
||||
runtime = resolve_llm_runtime(cfg)
|
||||
if runtime == "ollama":
|
||||
o = cfg.ollama_local_port
|
||||
@@ -82,6 +84,14 @@ def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
||||
AccessLink("Models", f"http://127.0.0.1:{p}/v1/models", "list"),
|
||||
]
|
||||
)
|
||||
if not links:
|
||||
links.append(
|
||||
AccessLink(
|
||||
"Туннель",
|
||||
"gpu-rent tunnel",
|
||||
"нет сервисов — ENABLE_SWARMUI / LLM_RUNTIME",
|
||||
)
|
||||
)
|
||||
else:
|
||||
links.append(
|
||||
AccessLink(
|
||||
@@ -94,6 +104,8 @@ def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
||||
|
||||
|
||||
def mcp_snippet_lines(cfg: Config) -> list[str]:
|
||||
if not bool(getattr(cfg, "enable_swarmui", True)):
|
||||
return ["# SwarmUI MCP skip (llm-only)"]
|
||||
port = cfg.swarmui_local_port
|
||||
return [
|
||||
"{",
|
||||
|
||||
@@ -24,14 +24,18 @@ def run_bootstrap(
|
||||
update: bool = True,
|
||||
light: bool = False,
|
||||
) -> None:
|
||||
if light:
|
||||
skip_swarm = not bool(getattr(cfg, "enable_swarmui", True))
|
||||
if skip_swarm:
|
||||
log("bootstrap llm-only (data disk, без SwarmUI)")
|
||||
elif light:
|
||||
log("bootstrap SwarmUI (light: без apt)")
|
||||
else:
|
||||
log("bootstrap SwarmUI на VM (идемпотентно, без Docker)")
|
||||
if update:
|
||||
log("git update: SwarmUI on")
|
||||
else:
|
||||
log("git update: SwarmUI off (--no-update)")
|
||||
if not skip_swarm:
|
||||
if update:
|
||||
log("git update: SwarmUI on")
|
||||
else:
|
||||
log("git update: SwarmUI off (--no-update)")
|
||||
script = bootstrap_script()
|
||||
out = run_script_sudo(
|
||||
cfg,
|
||||
@@ -43,9 +47,13 @@ def run_bootstrap(
|
||||
"SWARM_USER": cfg.ssh_user,
|
||||
"GPU_RENT_UPDATE_GIT": "1" if update else "0",
|
||||
"GPU_RENT_BOOTSTRAP_LIGHT": "1" if light else "0",
|
||||
"GPU_RENT_SKIP_SWARMUI": "1" if skip_swarm else "0",
|
||||
},
|
||||
log=log,
|
||||
)
|
||||
if "bootstrap ok" not in out:
|
||||
raise CloudError(f"bootstrap не подтвердил успех:\n{out[-800:]}")
|
||||
log("диски и systemd unit готовы; дальше seed, потом start swarmui")
|
||||
if skip_swarm:
|
||||
log("data disk готов — дальше LLM")
|
||||
else:
|
||||
log("диски и systemd unit готовы; дальше seed, потом start swarmui")
|
||||
|
||||
+95
-21
@@ -303,10 +303,10 @@ def status() -> None:
|
||||
def open(
|
||||
llm: bool = typer.Option(False, "--llm", help="Открыть LLM API URL вместо SwarmUI"),
|
||||
) -> None:
|
||||
"""Открыть браузер на SwarmUI :17801 (или --llm на Ollama/llama.cpp)."""
|
||||
"""Открыть браузер на SwarmUI :17801 (или --llm / llm-only на Ollama/llama.cpp)."""
|
||||
cfg = load_config(require_auth=False)
|
||||
if llm:
|
||||
from gpu_rent.llm_runtime import normalize_runtime
|
||||
use_llm = llm or not bool(getattr(cfg, "enable_swarmui", True))
|
||||
if use_llm:
|
||||
from gpu_rent.access_card import resolve_llm_runtime
|
||||
|
||||
runtime = resolve_llm_runtime(cfg)
|
||||
@@ -398,18 +398,25 @@ def up(
|
||||
),
|
||||
ollama: bool = typer.Option(False, "--ollama", help="То же что --llm ollama"),
|
||||
llamacpp: bool = typer.Option(False, "--llamacpp", help="То же что --llm llamacpp"),
|
||||
no_swarm: bool = typer.Option(
|
||||
False,
|
||||
"--no-swarm",
|
||||
"--llm-only",
|
||||
help="Только LLM на GPU, без установки SwarmUI",
|
||||
),
|
||||
) -> None:
|
||||
"""Create/unshelve GPU, bootstrap SwarmUI, по умолчанию туннель на :17801."""
|
||||
"""Create/unshelve GPU; SwarmUI и/или LLM; по умолчанию туннель."""
|
||||
try:
|
||||
from dataclasses import replace
|
||||
|
||||
from gpu_rent.llm_runtime import (
|
||||
append_vars_llm_runtime,
|
||||
decide_runtime,
|
||||
ensure_ollama_manifest_from_example,
|
||||
write_ollama_models_preset,
|
||||
)
|
||||
from gpu_rent.paths import vars_path
|
||||
from gpu_rent.prompts import MenuItem, prompt_menu
|
||||
from gpu_rent.varsfile import upsert_vars
|
||||
|
||||
checks = run_doctor()
|
||||
code = _print_checks(checks, quiet=not verbose)
|
||||
@@ -427,33 +434,93 @@ def up(
|
||||
except ValueError as exc:
|
||||
raise GpuRentError(str(exc)) from exc
|
||||
|
||||
enable_swarm = False if no_swarm else cfg.enable_swarmui
|
||||
asked_model_preset = False
|
||||
if not yes and runtime == "none" and not llm and not ollama and not llamacpp:
|
||||
llm_flags = bool(llm or ollama or llamacpp or no_swarm)
|
||||
|
||||
if not yes and not llm_flags:
|
||||
from gpu_rent.llm_runtime import (
|
||||
llamacpp_preset_menu,
|
||||
llm_runtime_menu,
|
||||
ollama_preset_menu,
|
||||
workload_menu,
|
||||
)
|
||||
from gpu_rent.prompts import prompt_menu
|
||||
|
||||
def _ask(msg: str, default: str = "") -> str:
|
||||
return typer.prompt(msg, default=default)
|
||||
|
||||
default_stack = (
|
||||
"llm"
|
||||
if not cfg.enable_swarmui
|
||||
else ("both" if runtime != "none" else "swarm")
|
||||
)
|
||||
try:
|
||||
choice = prompt_menu(
|
||||
"LLM рядом со SwarmUI",
|
||||
llm_runtime_menu(),
|
||||
default="none",
|
||||
stack = prompt_menu(
|
||||
"Что поднять на GPU",
|
||||
workload_menu(),
|
||||
default=default_stack,
|
||||
ask=_ask,
|
||||
show=log,
|
||||
)
|
||||
runtime = decide_runtime(
|
||||
flag=choice, ollama_flag=False, llamacpp_flag=False, from_config="none"
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise GpuRentError(str(exc)) from exc
|
||||
if runtime != "none" and typer.confirm("Запомнить LLM_RUNTIME в gpu-rent.vars?", default=True):
|
||||
append_vars_llm_runtime(vars_path(), runtime)
|
||||
|
||||
if stack == "swarm":
|
||||
enable_swarm = True
|
||||
runtime = "none"
|
||||
elif stack == "both":
|
||||
enable_swarm = True
|
||||
if runtime == "none":
|
||||
try:
|
||||
choice = prompt_menu(
|
||||
"LLM runtime",
|
||||
[
|
||||
MenuItem("ollama", "Ollama (+ pull моделей)"),
|
||||
MenuItem("llamacpp", "llama.cpp server (+ GGUF)"),
|
||||
],
|
||||
default="ollama",
|
||||
ask=_ask,
|
||||
show=log,
|
||||
)
|
||||
runtime = decide_runtime(
|
||||
flag=choice,
|
||||
ollama_flag=False,
|
||||
llamacpp_flag=False,
|
||||
from_config="none",
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise GpuRentError(str(exc)) from exc
|
||||
else:
|
||||
enable_swarm = False
|
||||
if runtime == "none":
|
||||
try:
|
||||
choice = prompt_menu(
|
||||
"LLM runtime",
|
||||
[
|
||||
MenuItem("ollama", "Ollama (+ pull моделей)"),
|
||||
MenuItem("llamacpp", "llama.cpp server (+ GGUF)"),
|
||||
],
|
||||
default="llamacpp",
|
||||
ask=_ask,
|
||||
show=log,
|
||||
)
|
||||
runtime = decide_runtime(
|
||||
flag=choice,
|
||||
ollama_flag=False,
|
||||
llamacpp_flag=False,
|
||||
from_config="none",
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise GpuRentError(str(exc)) from exc
|
||||
|
||||
if typer.confirm("Запомнить стек в gpu-rent.vars?", default=True):
|
||||
upsert_vars(
|
||||
vars_path(),
|
||||
{
|
||||
"ENABLE_SWARMUI": "true" if enable_swarm else "false",
|
||||
"LLM_RUNTIME": runtime,
|
||||
},
|
||||
)
|
||||
|
||||
if runtime == "ollama":
|
||||
ensure_ollama_manifest_from_example()
|
||||
try:
|
||||
@@ -498,7 +565,6 @@ def up(
|
||||
ollama_preset_menu,
|
||||
write_llamacpp_models_preset,
|
||||
)
|
||||
from gpu_rent.prompts import prompt_menu
|
||||
|
||||
def _ask2(msg: str, default: str = "") -> str:
|
||||
return typer.prompt(msg, default=default)
|
||||
@@ -529,9 +595,17 @@ def up(
|
||||
except ValueError as exc:
|
||||
raise GpuRentError(str(exc)) from exc
|
||||
|
||||
cfg = replace(cfg, llm_runtime=runtime)
|
||||
if runtime != "none":
|
||||
ok(f"LLM runtime: {runtime}")
|
||||
if not enable_swarm and runtime == "none":
|
||||
raise GpuRentError(
|
||||
"llm-only требует --ollama / --llamacpp / --llm … "
|
||||
"(или убери --no-swarm / ENABLE_SWARMUI=true)"
|
||||
)
|
||||
|
||||
cfg = replace(cfg, llm_runtime=runtime, enable_swarmui=enable_swarm)
|
||||
if enable_swarm:
|
||||
ok("стек: SwarmUI" + (f" + {runtime}" if runtime != "none" else ""))
|
||||
else:
|
||||
ok(f"стек: llm-only ({runtime})")
|
||||
|
||||
def confirm(msg: str) -> bool:
|
||||
return typer.confirm(msg)
|
||||
|
||||
@@ -31,6 +31,20 @@ def _as_bool(value: str | None, default: bool) -> bool:
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def parse_enable_swarmui(raw: str | None, *, default: bool = True) -> bool:
|
||||
"""ENABLE_SWARMUI / WORKLOAD=llm|swarm|both → whether to install SwarmUI."""
|
||||
wl = (os.environ.get("WORKLOAD") or "").strip().lower()
|
||||
if wl in {"llm", "llm-only", "llm_only", "llama", "ollama-only"}:
|
||||
return False
|
||||
if wl in {"swarm", "swarmui", "ui"}:
|
||||
return True
|
||||
if wl in {"both", "all", "full"}:
|
||||
return True
|
||||
if raw is None or str(raw).strip() == "":
|
||||
return default
|
||||
return _as_bool(str(raw), default)
|
||||
|
||||
|
||||
def _as_int(value: str | None, default: int) -> int:
|
||||
if value is None or value.strip() == "":
|
||||
return default
|
||||
@@ -84,6 +98,7 @@ class Config:
|
||||
update_git: bool
|
||||
|
||||
llm_runtime: str
|
||||
enable_swarmui: bool
|
||||
ollama_models_manifest: Path
|
||||
llamacpp_models_manifest: Path
|
||||
ollama_local_port: int
|
||||
@@ -169,6 +184,8 @@ def load_config(*, require_auth: bool = True) -> Config:
|
||||
except ValueError:
|
||||
llm_runtime = "none"
|
||||
|
||||
enable_swarmui = parse_enable_swarmui(os.environ.get("ENABLE_SWARMUI"), default=True)
|
||||
|
||||
def _dir(env_name: str, folder: str) -> Path:
|
||||
raw = (os.environ.get(env_name) or "").strip()
|
||||
return Path(raw).expanduser() if raw else (root / folder)
|
||||
@@ -210,6 +227,7 @@ def load_config(*, require_auth: bool = True) -> Config:
|
||||
swarmui_image=(os.environ.get("SWARMUI_IMAGE") or "").strip(),
|
||||
update_git=_as_bool(os.environ.get("UPDATE_GIT"), True),
|
||||
llm_runtime=llm_runtime,
|
||||
enable_swarmui=enable_swarmui,
|
||||
ollama_models_manifest=ollama_manifest,
|
||||
llamacpp_models_manifest=llamacpp_manifest,
|
||||
ollama_local_port=_as_int(os.environ.get("OLLAMA_LOCAL_PORT"), 17811),
|
||||
|
||||
@@ -106,7 +106,7 @@ def install_units(cfg: Config, host: str, log: Log) -> None:
|
||||
)
|
||||
service = f"""[Unit]
|
||||
Description=gpu-rent idle-killer (one shot)
|
||||
After=network-online.target swarmui.service
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
@@ -71,6 +71,12 @@ LLM_RUNTIME_LABELS: dict[str, str] = {
|
||||
"llamacpp": "llama.cpp server (+ GGUF)",
|
||||
}
|
||||
|
||||
WORKLOAD_LABELS: dict[str, str] = {
|
||||
"swarm": "только SwarmUI",
|
||||
"both": "SwarmUI + LLM",
|
||||
"llm": "только LLM (без SwarmUI)",
|
||||
}
|
||||
|
||||
|
||||
def llm_runtime_menu() -> list:
|
||||
from gpu_rent.prompts import MenuItem
|
||||
@@ -78,6 +84,12 @@ def llm_runtime_menu() -> list:
|
||||
return [MenuItem(k, f"{k} — {LLM_RUNTIME_LABELS[k]}") for k in ("none", "ollama", "llamacpp")]
|
||||
|
||||
|
||||
def workload_menu() -> list:
|
||||
from gpu_rent.prompts import MenuItem
|
||||
|
||||
return [MenuItem(k, WORKLOAD_LABELS[k]) for k in ("swarm", "both", "llm")]
|
||||
|
||||
|
||||
def ollama_preset_menu(*, include_keep: bool = False) -> list:
|
||||
from gpu_rent.prompts import MenuItem
|
||||
|
||||
|
||||
+57
-25
@@ -440,33 +440,68 @@ def provision_vm(
|
||||
server_id: str | None = None,
|
||||
update: bool = True,
|
||||
) -> None:
|
||||
from gpu_rent.llm_runtime import normalize_runtime
|
||||
from gpu_rent.state import load_state, save_state
|
||||
|
||||
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
||||
rt = normalize_runtime(cfg.llm_runtime)
|
||||
if not swarm and rt == "none":
|
||||
raise CloudError(
|
||||
"llm-only: нужен LLM_RUNTIME=ollama|llamacpp (или --ollama / --llamacpp)"
|
||||
)
|
||||
|
||||
restart = bool(update)
|
||||
try:
|
||||
if seed_extensions(cfg, host, log, update=update):
|
||||
restart = True
|
||||
except GpuRentError as exc:
|
||||
log(f"extensions: {exc}")
|
||||
raise
|
||||
try:
|
||||
if seed_autocomplete(cfg, host, log):
|
||||
restart = True
|
||||
except GpuRentError as exc:
|
||||
log(f"autocomplete: {exc}")
|
||||
seed_civitai(cfg, host, log)
|
||||
push_tree(cfg, host, cfg.local_models_dir, f"{DATA}/Models", log, models=True)
|
||||
push_tree(cfg, host, cfg.local_wildcards_dir, f"{DATA}/Data/Wildcards", log, models=False)
|
||||
push_tree(cfg, host, cfg.local_workflows_dir, f"{DATA}/CustomWorkflows", log, models=False)
|
||||
if cfg.pull_output:
|
||||
pull_tree(cfg, host, f"{DATA}/Output", cfg.local_output_dir, log)
|
||||
if swarm:
|
||||
try:
|
||||
if seed_extensions(cfg, host, log, update=update):
|
||||
restart = True
|
||||
except GpuRentError as exc:
|
||||
log(f"extensions: {exc}")
|
||||
raise
|
||||
try:
|
||||
if seed_autocomplete(cfg, host, log):
|
||||
restart = True
|
||||
except GpuRentError as exc:
|
||||
log(f"autocomplete: {exc}")
|
||||
seed_civitai(cfg, host, log)
|
||||
push_tree(cfg, host, cfg.local_models_dir, f"{DATA}/Models", log, models=True)
|
||||
push_tree(
|
||||
cfg, host, cfg.local_wildcards_dir, f"{DATA}/Data/Wildcards", log, models=False
|
||||
)
|
||||
push_tree(
|
||||
cfg,
|
||||
host,
|
||||
cfg.local_workflows_dir,
|
||||
f"{DATA}/CustomWorkflows",
|
||||
log,
|
||||
models=False,
|
||||
)
|
||||
if cfg.pull_output:
|
||||
pull_tree(cfg, host, f"{DATA}/Output", cfg.local_output_dir, log)
|
||||
else:
|
||||
log("SwarmUI: skip (llm-only)")
|
||||
run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
"sudo -n systemctl stop swarmui 2>/dev/null; "
|
||||
"sudo -n systemctl disable swarmui 2>/dev/null || true; "
|
||||
"echo llm-only | sudo -n tee /mnt/swarm_data/.gpu-rent-llm-only >/dev/null",
|
||||
check=False,
|
||||
)
|
||||
|
||||
try:
|
||||
probe_gpu(cfg, host, log)
|
||||
except Exception as exc:
|
||||
log(f"GPU probe: {exc}")
|
||||
|
||||
ensure_swarmui_running(cfg, host, log, restart=restart)
|
||||
|
||||
from gpu_rent.state import load_state, save_state
|
||||
if swarm:
|
||||
ensure_swarmui_running(cfg, host, log, restart=restart)
|
||||
run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
"sudo -n rm -f /mnt/swarm_data/.gpu-rent-llm-only",
|
||||
check=False,
|
||||
)
|
||||
|
||||
try:
|
||||
provision_llm(cfg, host, log)
|
||||
@@ -474,7 +509,6 @@ def provision_vm(
|
||||
st = load_state()
|
||||
st.notes = dict(st.notes or {})
|
||||
st.notes["llm_error"] = str(exc)[:500]
|
||||
# Do not claim success — leave previous notes.llm_runtime or clear to none.
|
||||
st.notes["llm_runtime"] = "none"
|
||||
save_state(st)
|
||||
raise CloudError(f"LLM runtime: {exc}") from exc
|
||||
@@ -498,10 +532,8 @@ def provision_vm(
|
||||
"⚠ idle-killer НЕ вооружён — GPU может тарифицироваться без авто-stop. "
|
||||
"См. status / docs/setup.md"
|
||||
)
|
||||
log("SwarmUI слушает 127.0.0.1:7801 — gpu-rent tunnel")
|
||||
from gpu_rent.llm_runtime import normalize_runtime
|
||||
|
||||
rt = normalize_runtime(cfg.llm_runtime)
|
||||
if swarm:
|
||||
log("SwarmUI слушает 127.0.0.1:7801 — gpu-rent tunnel")
|
||||
if rt == "ollama":
|
||||
log(f"Ollama API → localhost:{cfg.ollama_local_port} (туннель)")
|
||||
elif rt == "llamacpp":
|
||||
|
||||
@@ -95,7 +95,26 @@ mkdir -p \
|
||||
"${DATA_ROOT}/dlbackend" \
|
||||
"${DATA_ROOT}/Extensions" \
|
||||
"${DATA_ROOT}/DLNodes" \
|
||||
"${DATA_ROOT}/CustomWorkflows"
|
||||
"${DATA_ROOT}/CustomWorkflows" \
|
||||
"${DATA_ROOT}/ollama" \
|
||||
"${DATA_ROOT}/llamacpp/models"
|
||||
|
||||
# LLM-only: data disk + tools, no SwarmUI clone / unit.
|
||||
if [[ "${GPU_RENT_SKIP_SWARMUI:-0}" == "1" ]]; then
|
||||
chown -R "${SWARM_USER}:${SWARM_USER}" "$DATA_ROOT"
|
||||
date -u +"%Y-%m-%dT%H:%M:%SZ" >"$MARKER_DATA"
|
||||
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${DATA_ROOT}/.gpu-rent-llm-only"
|
||||
chown "${SWARM_USER}:${SWARM_USER}" "$MARKER_DATA" "${DATA_ROOT}/.gpu-rent-llm-only"
|
||||
systemctl stop swarmui 2>/dev/null || true
|
||||
systemctl disable swarmui 2>/dev/null || true
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
nvidia-smi -L || true
|
||||
fi
|
||||
log "bootstrap ok (llm-only; без SwarmUI)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -f "${DATA_ROOT}/.gpu-rent-llm-only" 2>/dev/null || true
|
||||
|
||||
if [[ ! -d "${SWARM_ROOT}/.git" ]]; then
|
||||
log "clone SwarmUI -> ${SWARM_ROOT}"
|
||||
|
||||
@@ -236,7 +236,10 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
swarm_url = str(creds.get("swarm_url") or "http://127.0.0.1:7801")
|
||||
busy, detail = swarm_busy(swarm_url)
|
||||
if (DATA / ".gpu-rent-llm-only").is_file():
|
||||
busy, detail = False, "llm-only (swarm skip)"
|
||||
else:
|
||||
busy, detail = swarm_busy(swarm_url)
|
||||
if busy:
|
||||
write_ts(IDLE_SINCE, None)
|
||||
log(f"busy: {detail}")
|
||||
|
||||
+24
-21
@@ -86,27 +86,26 @@ def _bind_access(
|
||||
log(f"SSH {cfg.ssh_user}@{ip}")
|
||||
state.phase = "bootstrapping"
|
||||
save_state(state)
|
||||
if update:
|
||||
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
||||
if swarm and update:
|
||||
active = run_ssh(cfg, ip, "systemctl is-active swarmui 2>/dev/null || true", check=False).strip()
|
||||
if active == "active":
|
||||
log("systemctl stop swarmui перед git update")
|
||||
run_ssh(cfg, ip, "sudo -n systemctl stop swarmui", timeout=120, check=False)
|
||||
# Skip apt-heavy bootstrap when the VM already finished first-boot.
|
||||
marker = run_ssh(
|
||||
cfg,
|
||||
ip,
|
||||
"test -f /opt/swarmui/.gpu-rent-bootstrapped && echo yes || echo no",
|
||||
check=False,
|
||||
).strip()
|
||||
if swarm:
|
||||
marker_cmd = "test -f /opt/swarmui/.gpu-rent-bootstrapped && echo yes || echo no"
|
||||
else:
|
||||
marker_cmd = "test -f /mnt/swarm_data/.gpu-rent-ready && echo yes || echo no"
|
||||
marker = run_ssh(cfg, ip, marker_cmd, check=False).strip()
|
||||
if marker == "yes":
|
||||
# VM marker survives stop; local bootstrapped is cleared on stop — still light.
|
||||
if not state.bootstrapped:
|
||||
log("маркер bootstrap на VM — лёгкий проход (локальный bootstrapped был сброшен)")
|
||||
else:
|
||||
log("bootstrap уже на VM — лёгкий проход (без apt)")
|
||||
run_bootstrap(cfg, ip, log, update=update, light=True)
|
||||
run_bootstrap(cfg, ip, log, update=update and swarm, light=True)
|
||||
else:
|
||||
run_bootstrap(cfg, ip, log, update=update, light=False)
|
||||
run_bootstrap(cfg, ip, log, update=update and swarm, light=False)
|
||||
provision_vm(
|
||||
cfg,
|
||||
ip,
|
||||
@@ -115,17 +114,19 @@ def _bind_access(
|
||||
server_id=getattr(server, "id", None) or state.server_id,
|
||||
update=update,
|
||||
)
|
||||
try:
|
||||
wait_backend_idle(cfg, ip, log)
|
||||
except CloudError as exc:
|
||||
log(f"ready: {exc}")
|
||||
# Comfy venv + Backends.fds exist after Idle — sage/triton + ExtraArgs.
|
||||
try:
|
||||
if tune_swarm_perf(cfg, ip, log):
|
||||
log("systemctl restart swarmui (perf ExtraArgs)")
|
||||
run_ssh(cfg, ip, "sudo -n systemctl restart swarmui", timeout=120)
|
||||
except Exception as exc:
|
||||
log(f"perf tune: {exc}")
|
||||
if swarm:
|
||||
try:
|
||||
wait_backend_idle(cfg, ip, log)
|
||||
except CloudError as exc:
|
||||
log(f"ready: {exc}")
|
||||
try:
|
||||
if tune_swarm_perf(cfg, ip, log):
|
||||
log("systemctl restart swarmui (perf ExtraArgs)")
|
||||
run_ssh(cfg, ip, "sudo -n systemctl restart swarmui", timeout=120)
|
||||
except Exception as exc:
|
||||
log(f"perf tune: {exc}")
|
||||
else:
|
||||
log("ready: llm-only (без ожидания SwarmUI Idle)")
|
||||
try:
|
||||
ensure_boot_snapshot(
|
||||
conn,
|
||||
@@ -138,6 +139,8 @@ def _bind_access(
|
||||
notify_ready(cfg, log)
|
||||
state.bootstrapped = True
|
||||
state.phase = "ready_cloud"
|
||||
state.notes = dict(state.notes or {})
|
||||
state.notes["enable_swarmui"] = swarm
|
||||
save_state(state)
|
||||
return state
|
||||
|
||||
|
||||
+18
-7
@@ -72,16 +72,18 @@ def decide_watch(status: str | None, tunnel_alive: bool) -> WatchDecision:
|
||||
|
||||
|
||||
def tunnel_forwards(cfg: Config) -> list[tuple[int, int]]:
|
||||
"""List of (local_port, remote_port). SwarmUI always; LLM if configured.
|
||||
|
||||
Prefer live config (`LLM_RUNTIME`) over stale state.notes.
|
||||
"""
|
||||
pairs = [(cfg.swarmui_local_port, 7801)]
|
||||
"""List of (local_port, remote_port). SwarmUI if enabled; LLM if configured."""
|
||||
pairs: list[tuple[int, int]] = []
|
||||
if bool(getattr(cfg, "enable_swarmui", True)):
|
||||
pairs.append((cfg.swarmui_local_port, 7801))
|
||||
runtime = normalize_runtime(cfg.llm_runtime)
|
||||
if runtime == "ollama":
|
||||
pairs.append((cfg.ollama_local_port, 11434))
|
||||
elif runtime == "llamacpp":
|
||||
pairs.append((cfg.llamacpp_local_port, 8080))
|
||||
if not pairs:
|
||||
# Failsafe: at least SwarmUI port so tunnel isn't empty.
|
||||
pairs.append((cfg.swarmui_local_port, 7801))
|
||||
return pairs
|
||||
|
||||
|
||||
@@ -181,14 +183,23 @@ def run_tunnel(
|
||||
log("watchdog: EXPIRED → unshelve + reconnect")
|
||||
|
||||
server = _start_forwarder(cfg, current_host, forwards)
|
||||
swarm_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||
swarm_on = bool(getattr(cfg, "enable_swarmui", True))
|
||||
runtime = normalize_runtime(cfg.llm_runtime)
|
||||
if swarm_on:
|
||||
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||
elif runtime == "ollama":
|
||||
open_url = f"http://127.0.0.1:{cfg.ollama_local_port}"
|
||||
elif runtime == "llamacpp":
|
||||
open_url = f"http://127.0.0.1:{cfg.llamacpp_local_port}"
|
||||
else:
|
||||
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||
|
||||
from gpu_rent.access_card import print_access_card
|
||||
|
||||
print_access_card(cfg, tunneled=True, host=current_host)
|
||||
|
||||
if open_browser:
|
||||
webbrowser.open(swarm_url)
|
||||
webbrowser.open(open_url)
|
||||
|
||||
state = load_state()
|
||||
state.phase = "ready_tunneled"
|
||||
|
||||
Reference in New Issue
Block a user