mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add output filename suffixes, CPU fallback for GPU packages, and fix e2e tests
- Add tool-specific suffix to output filenames so downloads don't overwrite originals (batch & single-tool routes) - Skip deleting shared models when uninstalling a bundle that shares models with another installed bundle - Auto-detect NVIDIA GPU and swap GPU-only pip packages (onnxruntime-gpu, paddlepaddle-gpu) for CPU equivalents - Refactor docker-compose with YAML anchors and explicit cpu/gpu profiles - Add libheif-plugin-x265 to Dockerfile - Fix install-all queue logic to handle concurrent individual installs and clear stale errors - Unify playwright docker config to use same test dir with API_URL env var - Fix flaky e2e selectors, rename Strip Metadata → Remove Metadata, handle collage custom dropzone, improve fallback test image generation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,39 @@ def detect_arch() -> str:
|
||||
return "amd64"
|
||||
|
||||
|
||||
def has_nvidia_gpu() -> bool:
|
||||
"""Check whether an NVIDIA GPU is accessible at runtime."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
return result.returncode == 0 and len(result.stdout.strip()) > 0
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return False
|
||||
|
||||
|
||||
def cpu_fallback_packages(packages: list[str]) -> list[str]:
|
||||
"""Replace GPU-only packages with their CPU equivalents.
|
||||
|
||||
Called on amd64 when no NVIDIA GPU is detected so that onnxruntime /
|
||||
paddlepaddle don't crash with a CUDA segfault.
|
||||
"""
|
||||
replacements = {
|
||||
"onnxruntime-gpu": "onnxruntime",
|
||||
"paddlepaddle-gpu": "paddlepaddle",
|
||||
}
|
||||
result = []
|
||||
for pkg in packages:
|
||||
name = pkg.split("==")[0].split(">=")[0].split("[")[0].strip()
|
||||
if name in replacements:
|
||||
version = pkg[len(name):] # e.g. "==1.20.1"
|
||||
result.append(replacements[name] + version)
|
||||
else:
|
||||
result.append(pkg)
|
||||
return result
|
||||
|
||||
|
||||
def check_disk_space(path: str, min_bytes: int = 100 * 1024 * 1024) -> None:
|
||||
"""Exit with a clear error if free disk space is below min_bytes."""
|
||||
try:
|
||||
@@ -94,6 +127,13 @@ def install_packages(bundle: dict, arch: str) -> None:
|
||||
common_pkgs = packages_section.get("common", [])
|
||||
arch_pkgs = packages_section.get(arch, [])
|
||||
all_pkgs = common_pkgs + arch_pkgs
|
||||
|
||||
# On amd64 without GPU, swap GPU packages for CPU equivalents to avoid
|
||||
# segfaults from onnxruntime-gpu / paddlepaddle-gpu trying to init CUDA.
|
||||
if arch == "amd64" and not has_nvidia_gpu():
|
||||
all_pkgs = cpu_fallback_packages(all_pkgs)
|
||||
sys.stderr.write("No NVIDIA GPU detected — using CPU package variants\n")
|
||||
sys.stderr.flush()
|
||||
pip_flags = bundle.get("pipFlags", {})
|
||||
post_install = bundle.get("postInstall", [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user