Fix SAM symlink crash and make prefetch-models.sh resilient per-step
download_sam_model.py crashed with an uncaught PermissionError when data/models/ is root-owned (common after a prior Docker run) and a non-root host user tries to recreate the convenience sam_model.pth symlink — observed in the wild, and it aborted the whole prefetch run before U2Net/BEN2/BiRefNet-HR were ever attempted. Worse, the same unguarded symlink call sat inside the post-download try/except, so a successful download could get deleted just because the symlink step failed afterward. Wrapped symlink creation in a shared helper that warns and continues instead of raising — the real model file already satisfies entrypoint.sh's checks regardless of the symlink. prefetch-models.sh now treats SAM, U2Net, and the HuggingFace models as independent steps (one failing no longer aborts the rest) and prints a summary of which steps failed, so a single run gives full diagnostic signal instead of stopping at the first error.
This commit is contained in:
+22
-6
@@ -12,9 +12,11 @@
|
||||
# ./prefetch-models.sh --sdxl # also prefetch SDXL base + inpaint (~13GB)
|
||||
#
|
||||
# Safe to re-run: every download here skips files that already exist
|
||||
# (HuggingFace Hub) or are already present (SAM/U2Net).
|
||||
# (HuggingFace Hub) or are already present (SAM/U2Net). Each model is
|
||||
# independent — one failing (e.g. no network reachable at all) doesn't
|
||||
# block the others from being attempted.
|
||||
|
||||
set -euo pipefail
|
||||
set -uo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
@@ -36,26 +38,29 @@ if [ "${1:-}" = "--sdxl" ]; then
|
||||
PREFETCH_SDXL=1
|
||||
fi
|
||||
|
||||
FAILED=()
|
||||
|
||||
echo "=================================================="
|
||||
echo " Prefetching AI models (host-side, no Docker)"
|
||||
echo "=================================================="
|
||||
|
||||
echo ""
|
||||
echo "── SAM (Smart Select) ───────────────────────────────"
|
||||
python3 scripts/download_sam_model.py vit_b
|
||||
python3 scripts/download_sam_model.py vit_b || FAILED+=("SAM")
|
||||
|
||||
echo ""
|
||||
echo "── U2Net (Remove Background fallback) ──────────────"
|
||||
python3 scripts/download_u2net_model.py u2net
|
||||
python3 scripts/download_u2net_model.py u2net || FAILED+=("U2Net")
|
||||
|
||||
echo ""
|
||||
echo "── HuggingFace Hub models (BEN2, BiRefNet-HR) ───────"
|
||||
|
||||
if ! python3 -c "import huggingface_hub" &>/dev/null; then
|
||||
echo "Installing huggingface_hub (lightweight — no torch/GPU needed for this step)..."
|
||||
python3 -m pip install --quiet --user "huggingface_hub>=0.23.0"
|
||||
python3 -m pip install --quiet --user "huggingface_hub>=0.23.0" || FAILED+=("huggingface_hub install")
|
||||
fi
|
||||
|
||||
if python3 -c "import huggingface_hub" &>/dev/null; then
|
||||
# HF_HOME must match what the container resolves by default: the bind mount
|
||||
# maps ./data/hf_cache -> /root/.cache/huggingface, and the container never
|
||||
# sets HF_HOME explicitly, so it defaults to ~/.cache/huggingface there.
|
||||
@@ -65,7 +70,7 @@ fi
|
||||
# same way, rather than us hardcoding it and risking a mismatch.
|
||||
export HF_HOME="$(pwd)/data/hf_cache"
|
||||
|
||||
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF'
|
||||
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF' || FAILED+=("HuggingFace models")
|
||||
import os
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
@@ -81,12 +86,23 @@ for repo_id in repos:
|
||||
snapshot_download(repo_id=repo_id, ignore_patterns=["*.msgpack", "flax_*", "tf_*"])
|
||||
print(f" done: {repo_id}")
|
||||
PYEOF
|
||||
else
|
||||
echo "⚠ Skipping BEN2/BiRefNet-HR — huggingface_hub unavailable (install failed above)"
|
||||
FAILED+=("HuggingFace models")
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
if [ ${#FAILED[@]} -eq 0 ]; then
|
||||
echo " Done. Models cached under ./data/models and ./data/hf_cache"
|
||||
if [ "$PREFETCH_SDXL" != "1" ]; then
|
||||
echo " (SDXL not included — re-run with --sdxl to also prefetch txt2img/inpaint, ~13GB)"
|
||||
fi
|
||||
echo " Start the app: ./bring-up-local-gpu.sh"
|
||||
else
|
||||
echo " Finished with failures: ${FAILED[*]}"
|
||||
echo " If ALL of the above failed, this host can't reach the internet right now"
|
||||
echo " (check: curl -v https://github.com) — that's a host/network issue, not Docker."
|
||||
echo " If only some failed, re-run this script to retry just those."
|
||||
fi
|
||||
echo "=================================================="
|
||||
|
||||
@@ -40,6 +40,20 @@ SAM_MODELS = {
|
||||
}
|
||||
}
|
||||
|
||||
def create_symlink(symlink_path: Path, target_name: str):
|
||||
"""Best-effort convenience symlink. Never raises — a missing/stale
|
||||
symlink is harmless (callers also check the real filename directly),
|
||||
but data/models/ is often root-owned from a prior Docker run, which
|
||||
makes unlink/symlink_to fail with PermissionError for other users."""
|
||||
try:
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(target_name)
|
||||
print(f"Symlink created: {symlink_path} -> {target_name}")
|
||||
except OSError as e:
|
||||
print(f"(skipping symlink: {e})")
|
||||
|
||||
|
||||
def download_with_progress(url: str, dest_path: Path):
|
||||
"""Download file with progress indicator"""
|
||||
print(f"Downloading to: {dest_path}")
|
||||
@@ -88,12 +102,7 @@ def main():
|
||||
print(f"\nModel already exists at: {dest_path}")
|
||||
print("To re-download, delete the file first.")
|
||||
|
||||
# Create symlink for easy access
|
||||
symlink_path = models_dir / 'sam_model.pth'
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(dest_path.name)
|
||||
print(f"Symlink created: {symlink_path} -> {dest_path.name}")
|
||||
create_symlink(models_dir / 'sam_model.pth', dest_path.name)
|
||||
return
|
||||
|
||||
print(f"\nDownloading SAM {model_type.upper()} ({model_info['size']})...")
|
||||
@@ -103,17 +112,13 @@ def main():
|
||||
try:
|
||||
download_with_progress(model_info['url'], dest_path)
|
||||
|
||||
# Create symlink for easy access
|
||||
symlink_path = models_dir / 'sam_model.pth'
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(dest_path.name)
|
||||
create_symlink(symlink_path, dest_path.name)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("SUCCESS!")
|
||||
print(f"Model saved to: {dest_path}")
|
||||
print(f"Symlink: {symlink_path}")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user