1#!/usr/bin/env python3
2"""Download pinned model files once, with resumable transfers and SHA-256 checks."""
3import argparse
4import fcntl
5import hashlib
6import json
7import os
8from pathlib import Path
9import subprocess
10
11
12def checksum(path):
13 with path.open("rb") as source:
14 return hashlib.file_digest(source, "sha256").hexdigest()
15
16
17def download(root):
18 manifest = json.loads(Path(__file__).with_name("models.json").read_text())
19 directory = root / manifest["directory"]
20 directory.mkdir(parents=True, exist_ok=True)
21 with (directory / ".download.lock").open("a") as lock:
22 fcntl.flock(lock, fcntl.LOCK_EX)
23 for spec in manifest["files"]:
24 target = directory / spec["name"]
25 if target.exists():
26 if target.stat().st_size == spec["bytes"] and checksum(target) == spec["sha256"]:
27 print(f"Verified {target.name}", flush=True)
28 continue
29 raise ValueError(f"Checksum mismatch: {target}. Move this file aside and retry.")
30 pending = target.with_suffix(target.suffix + ".part")
31 url = f"https://huggingface.co/{manifest['repository']}/resolve/{manifest['revision']}/{spec['name']}"
32 print(f"Downloading {target.name}", flush=True)
33 subprocess.run([
34 "curl", "--fail", "--location", "--silent", "--show-error",
35 "--continue-at", "-", "--retry", "3", "--retry-delay", "3",
36 "--connect-timeout", "15", "--max-time", "28800",
37 "--output", str(pending), url,
38 ], check=True)
39 if pending.stat().st_size != spec["bytes"] or checksum(pending) != spec["sha256"]:
40 raise ValueError(f"Checksum mismatch: {pending}. Move this file aside and retry.")
41 pending.chmod(0o640)
42 pending.replace(target)
43 print(f"Verified {target.name}", flush=True)
44 (directory / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
45
46
47if __name__ == "__main__":
48 parser = argparse.ArgumentParser(description="Download local AI models")
49 parser.add_argument("--root", type=Path, default=Path("/srv/clover/Media/AI/LLM"))
50 args = parser.parse_args()
51 os.umask(0o027)
52 download(args.root)