Add support for gpu-rent.vars and enhance git update functionality

- Updated .gitignore to include gpu-rent.vars.
- Modified env.example to introduce the UPDATE_GIT variable for controlling git updates during execution.
- Implemented Import-GpuRentVars function in gpu-rent.ps1 to load environment variables from gpu-rent.vars.
- Enhanced gpu-rent.sh to support loading variables from gpu-rent.vars and added logic for handling default and extra arguments.
- Updated CLI documentation to reflect the new gpu-rent.vars file and its usage in configuration.
- Improved bootstrap and provisioning logic to conditionally perform git updates based on the new configuration.
This commit is contained in:
Leonid Pershin
2026-08-21 05:01:53 +03:00
parent ec42830579
commit a9cf2e0f90
23 changed files with 515 additions and 39 deletions
+14 -1
View File
@@ -97,8 +97,21 @@ if [[ ! -d "${SWARM_ROOT}/.git" ]]; then
log "clone SwarmUI -> ${SWARM_ROOT}"
mkdir -p "$(dirname "$SWARM_ROOT")"
git clone --depth 1 "$SWARM_REPO" "$SWARM_ROOT"
elif [[ "${GPU_RENT_UPDATE_GIT:-1}" == "1" ]]; then
log "обновляю SwarmUI в ${SWARM_ROOT}"
branch="$(git -C "$SWARM_ROOT" remote show origin 2>/dev/null | sed -n '/HEAD branch/s/.*: //p' || true)"
branch="${branch:-master}"
# shallow clone: deepen tip of default branch
git -C "$SWARM_ROOT" fetch --depth 1 origin "$branch" || git -C "$SWARM_ROOT" fetch --depth 1 origin
if git -C "$SWARM_ROOT" rev-parse --verify -q "origin/${branch}" >/dev/null; then
git -C "$SWARM_ROOT" checkout -B "$branch" "origin/${branch}"
git -C "$SWARM_ROOT" reset --hard "origin/${branch}"
else
git -C "$SWARM_ROOT" pull --ff-only || true
fi
log "SwarmUI @ $(git -C "$SWARM_ROOT" rev-parse --short HEAD)"
else
log "SwarmUI уже в ${SWARM_ROOT}"
log "SwarmUI уже в ${SWARM_ROOT} (update off)"
fi
if [[ ! -x /usr/share/dotnet/dotnet && ! -x "/home/${SWARM_USER}/.dotnet/dotnet" ]]; then
+33 -5
View File
@@ -25,6 +25,19 @@ def sha256_path(path: Path) -> str:
return digest.hexdigest()
def should_skip(dest: Path, expect_sha: str) -> tuple[bool, str]:
"""Return (skip, reason). Existing file with matching sha — or any non-empty if no sha."""
if not dest.is_file() or dest.stat().st_size <= 0:
return False, ""
expect = (expect_sha or "").lower()
if not expect:
return True, "уже есть"
got = sha256_path(dest).lower()
if got == expect:
return True, "уже есть (sha ok)"
return False, "sha не совпал — перекачиваю"
def download(url: str, dest: Path, token: str) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
partial = dest.with_suffix(dest.suffix + ".partial")
@@ -61,16 +74,30 @@ def main() -> int:
return 1
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
jobs = json.loads(JOBS_PATH.read_text(encoding="utf-8"))
total = len(jobs)
failed = 0
for job in jobs:
skipped = 0
downloaded = 0
for index, job in enumerate(jobs, start=1):
dest = Path(job["dest"])
expect = (job.get("sha256") or "").lower()
if dest.is_file() and expect and sha256_path(dest).lower() == expect:
print(f"skip {dest}")
prefix = f"[{index}/{total}]"
skip, reason = should_skip(dest, expect)
if skip:
skipped += 1
print(f"{prefix} {reason}: {dest.name}")
# refresh sidecars even on skip
for extra_name, extra_text in (job.get("sidecars") or {}).items():
extra = dest.parent / extra_name
extra.parent.mkdir(parents=True, exist_ok=True)
extra.write_text(extra_text, encoding="utf-8")
continue
if reason:
print(f"{prefix} {reason}: {dest.name}")
try:
print(f"download {dest.name}")
print(f"{prefix} качаю: {dest.name}")
download(job["url"], dest, token)
downloaded += 1
if expect:
got = sha256_path(dest).lower()
if got != expect:
@@ -81,8 +108,9 @@ def main() -> int:
extra.write_text(extra_text, encoding="utf-8")
except Exception as exc:
failed += 1
print(f"FAIL {dest}: {exc}", file=sys.stderr)
print(f"{prefix} FAIL {dest.name}: {exc}", file=sys.stderr)
TOKEN_PATH.unlink(missing_ok=True)
print(f"Civitai итог: скачано {downloaded}, пропущено {skipped}, ошибок {failed} (из {total})")
if failed:
return 1
MARKER.parent.mkdir(parents=True, exist_ok=True)
+80 -7
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
"""Clone git extensions on the VM. Stdlib only. Token file optional."""
"""Clone/update git extensions on the VM. Stdlib only. Token file optional."""
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
@@ -10,7 +11,12 @@ from urllib.parse import urlsplit, urlunsplit
TOKEN_PATH = Path("/tmp/gpu-rent-git.token")
JOBS_PATH = Path("/tmp/gpu-rent-ext.json")
UPDATE_PATH = Path("/tmp/gpu-rent-update-git")
MARKER = Path("/mnt/swarm_data/.gpu-rent-extensions-seeded")
EXTRA_ROOTS = (
Path("/mnt/swarm_data/Extensions"),
Path("/mnt/swarm_data/DLNodes"),
)
def strip_auth(url: str) -> str:
@@ -35,25 +41,64 @@ def run(argv: list[str], cwd: str | None = None) -> None:
subprocess.check_call(argv, cwd=cwd)
def out(argv: list[str], cwd: str | None = None) -> str:
return subprocess.check_output(argv, cwd=cwd, text=True).strip()
def is_sha(ref: str) -> bool:
ref = ref.strip()
return len(ref) == 40 and all(c in "0123456789abcdefABCDEF" for c in ref)
def clone_one(job: dict, token: str) -> None:
def do_update() -> bool:
if UPDATE_PATH.is_file():
return UPDATE_PATH.read_text(encoding="utf-8").strip() not in {"0", "false", "no", "off"}
return (os.environ.get("GPU_RENT_UPDATE_GIT") or "1").strip() not in {"0", "false", "no", "off"}
def fetch_and_checkout(dest: Path, ref: str) -> None:
run(["git", "-C", str(dest), "fetch", "--recurse-submodules", "--tags", "origin"])
if is_sha(ref):
run(["git", "-C", str(dest), "checkout", "--detach", ref])
print(f"updated {dest} @ {ref[:12]}")
return
run(["git", "-C", str(dest), "checkout", ref])
# Move branch tip to remote (shallow-friendly).
try:
run(["git", "-C", str(dest), "reset", "--hard", f"origin/{ref}"])
except subprocess.CalledProcessError:
run(["git", "-C", str(dest), "pull", "--ff-only", "origin", ref])
print(f"updated {dest} ({ref})")
def update_tracking_branch(dest: Path) -> None:
branch = out(["git", "-C", str(dest), "rev-parse", "--abbrev-ref", "HEAD"])
if not branch or branch == "HEAD":
print(f"skip detached {dest}")
return
run(["git", "-C", str(dest), "fetch", "--recurse-submodules", "--tags", "origin"])
try:
run(["git", "-C", str(dest), "reset", "--hard", f"origin/{branch}"])
except subprocess.CalledProcessError:
run(["git", "-C", str(dest), "pull", "--ff-only"])
print(f"updated installed {dest} ({branch})")
def clone_one(job: dict, token: str, update: bool) -> None:
dest = Path(job["dest"])
url = job["url"].strip()
ref = (job.get("ref") or "main").strip()
dest.parent.mkdir(parents=True, exist_ok=True)
authed = with_token(url, token)
if dest.is_dir() and (dest / ".git").is_dir():
origin = subprocess.check_output(["git", "-C", str(dest), "remote", "get-url", "origin"], text=True).strip()
origin = out(["git", "-C", str(dest), "remote", "get-url", "origin"])
if strip_auth(origin) != strip_auth(url):
print(f"FAIL origin mismatch {dest}: {origin} != {url}", file=sys.stderr)
raise SystemExit(2)
run(["git", "-C", str(dest), "fetch", "--recurse-submodules", "origin"])
run(["git", "-C", str(dest), "checkout", ref])
print(f"updated {dest}")
if not update:
print(f"skip update {dest}")
return
fetch_and_checkout(dest, ref)
return
if dest.exists():
print(f"FAIL {dest} exists but is not a git repo", file=sys.stderr)
@@ -71,16 +116,44 @@ def clone_one(job: dict, token: str) -> None:
print(f"cloned {dest}")
def update_installed_extras(known: set[str], update: bool) -> None:
if not update:
return
for root in EXTRA_ROOTS:
if not root.is_dir():
continue
for child in sorted(root.iterdir()):
if not child.is_dir() or not (child / ".git").is_dir():
continue
key = str(child)
if key in known:
continue
try:
update_tracking_branch(child)
except Exception as exc:
print(f"FAIL installed {child}: {exc}", file=sys.stderr)
raise
def main() -> int:
token = TOKEN_PATH.read_text(encoding="utf-8").strip() if TOKEN_PATH.is_file() else ""
jobs = json.loads(JOBS_PATH.read_text(encoding="utf-8"))
update = do_update()
print(f"extensions update={'on' if update else 'off'}")
failed = 0
known: set[str] = set()
for job in jobs:
try:
clone_one(job, token)
dest = str(Path(job["dest"]))
known.add(dest)
clone_one(job, token, update)
except Exception as exc:
failed += 1
print(f"FAIL {job.get('dest')}: {exc}", file=sys.stderr)
try:
update_installed_extras(known, update)
except Exception:
failed += 1
if TOKEN_PATH.is_file():
TOKEN_PATH.unlink()
if failed: