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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user