SearchingMan's picture
Add model card, workflows, and verified release evidence
bcd319e verified
Raw
History Blame Contribute Delete
6.66 kB
#!/usr/bin/env python3
"""Dry-run by default uploader for the FLUX.1-dev ConvRot Hugging Face repo."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = ROOT / "release_manifest.json"
VERIFY_SCRIPT = ROOT / "scripts/verify_release.py"
VERIFICATION_PATH = ROOT / "release_verification.json"
CHUNK_BYTES = 16 * 1024 * 1024
def parse_args() -> argparse.Namespace:
manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-id", default=manifest["suggested_repo_id"])
parser.add_argument(
"--model-root",
type=Path,
default=Path(os.environ.get("COMFYUI_MODELS_ROOT", "/mnt/d/comfyui/models")),
)
parser.add_argument("--revision", default="main")
parser.add_argument("--execute", action="store_true", help="Perform network writes; default is dry-run.")
parser.add_argument(
"--support-only",
action="store_true",
help="Upload documentation/evidence only; do not re-upload model artifacts.",
)
parser.add_argument(
"--create-private",
action="store_true",
help="Create a new private model repository before upload.",
)
parser.add_argument(
"--accept-flux-license",
action="store_true",
help="Confirm that the uploader reviewed and accepts the upstream FLUX.1-dev license.",
)
return parser.parse_args()
def load_manifest() -> dict[str, Any]:
value = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise RuntimeError("release manifest must be a JSON object")
return value
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(CHUNK_BYTES), b""):
digest.update(chunk)
return digest.hexdigest()
def existing_verification_is_current(manifest: dict[str, Any], model_root: Path) -> bool:
if not VERIFICATION_PATH.is_file():
return False
verification = json.loads(VERIFICATION_PATH.read_text(encoding="utf-8"))
if verification.get("status") != "PASS" or not verification.get("model_hashes_verified"):
return False
if verification.get("artifact_total_bytes") != manifest.get("artifact_total_bytes"):
return False
support = {record["path"]: record for record in verification.get("support_files", [])}
for relative in manifest["required_repository_files"]:
record = support.get(relative)
path = ROOT / relative
if record is None or not path.is_file() or path.is_symlink():
return False
if path.stat().st_size != record["bytes"] or sha256_file(path) != record["sha256"]:
return False
verified_artifacts = {
record["repo_path"]: record for record in verification.get("artifacts", [])
}
verification_mtime_ns = VERIFICATION_PATH.stat().st_mtime_ns
for artifact in manifest["artifacts"]:
record = verified_artifacts.get(artifact["repo_path"])
source = model_root / artifact["source_relative_path"]
if record is None or not record.get("hash_verified") or not source.is_file():
return False
if record.get("sha256") != artifact["sha256"]:
return False
if source.stat().st_size != artifact["bytes"] or source.stat().st_mtime_ns > verification_mtime_ns:
return False
return True
def main() -> int:
args = parse_args()
manifest = load_manifest()
model_root = args.model_root.expanduser().resolve(strict=True)
plan = {
"mode": "execute" if args.execute else "dry-run",
"repo_id": args.repo_id,
"revision": args.revision,
"create_private": args.create_private,
"support_only": args.support_only,
"support_root": str(ROOT),
"artifact_total_bytes": manifest["artifact_total_bytes"],
"artifacts": [
{
"source": str(model_root / artifact["source_relative_path"]),
"repo_path": artifact["repo_path"],
"bytes": artifact["bytes"],
"sha256": artifact["sha256"],
}
for artifact in manifest["artifacts"]
],
}
print(json.dumps(plan, indent=2))
if not args.execute:
print("DRY RUN: no repository was created and no files were uploaded.")
return 0
if not args.accept_flux_license:
raise SystemExit("Refusing upload: pass --accept-flux-license after reviewing LICENSE_NOTICE.md")
if existing_verification_is_current(manifest, model_root):
print("Reusing current full-hash release_verification.json")
else:
subprocess.run(
[sys.executable, str(VERIFY_SCRIPT), "--model-root", str(model_root), "--models"],
check=True,
)
from huggingface_hub import HfApi
api = HfApi()
if args.create_private:
api.create_repo(repo_id=args.repo_id, repo_type="model", private=True, exist_ok=False)
support_paths = [
".gitattributes",
"LICENSE_NOTICE.md",
"README.md",
"assets/*",
"manifests/*",
"release_manifest.json",
"release_verification.json",
"scripts/*",
"workflows/*",
]
api.upload_folder(
repo_id=args.repo_id,
repo_type="model",
folder_path=str(ROOT),
path_in_repo=".",
revision=args.revision,
allow_patterns=support_paths,
ignore_patterns=["**/__pycache__/**", "**/*.pyc"],
commit_message="Add model card, workflows, and verified release evidence",
)
if args.support_only:
print(f"Support-file upload complete: https://huggingface.co/{args.repo_id}")
return 0
for artifact in manifest["artifacts"]:
source = model_root / artifact["source_relative_path"]
api.upload_file(
repo_id=args.repo_id,
repo_type="model",
path_or_fileobj=str(source),
path_in_repo=artifact["repo_path"],
revision=args.revision,
commit_message=f"Add {artifact['repo_path']}",
)
print(f"Upload complete: https://huggingface.co/{args.repo_id}")
print("If publication is intended, configure gating and review visibility in Hugging Face settings before making the repository public.")
return 0
if __name__ == "__main__":
raise SystemExit(main())