Spaces:
Running
Running
Download scripts/deploy_hf_space.py from Leon4gr45/openoperator: direct link, hf CLI and curl.
- Browser
- Download file 7.81 kB
-
https://huggingface.co/spaces/Leon4gr45/openoperator/resolve/main/scripts/deploy_hf_space.py
- Command line
-
hf download hf://spaces/Leon4gr45/openoperator/scripts/deploy_hf_space.py
-
curl -L -o deploy_hf_space.py https://huggingface.co/spaces/Leon4gr45/openoperator/resolve/main/scripts/deploy_hf_space.py
7.81 kB
| #!/usr/bin/env python3 | |
| """Deterministic deployment script to synchronize a green commit to Hugging Face Space.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import urllib.request | |
| from pathlib import Path | |
| def deployment_identity(root, requested, space): | |
| head = subprocess.check_output( | |
| ["git", "rev-parse", "HEAD"], cwd=root, text=True | |
| ).strip() | |
| dirty = bool( | |
| subprocess.check_output( | |
| ["git", "status", "--porcelain", "--untracked-files=all"], | |
| cwd=root, | |
| text=True, | |
| ) | |
| ) | |
| sha = subprocess.check_output( | |
| [ | |
| "git", | |
| "rev-parse", | |
| "--verify", | |
| "--end-of-options", | |
| (requested or head) + "^{commit}", | |
| ], | |
| cwd=root, | |
| text=True, | |
| ).strip() | |
| print( | |
| f"Local HEAD: {head}\nDeployment SHA: {sha}\nWorking tree clean: {not dirty}\nTarget HF Space: {space}" | |
| ) | |
| if dirty: | |
| raise RuntimeError( | |
| "Refusing deployment: working tree contains uncommitted changes. " | |
| "The HF deployment is built from a committed Git SHA. " | |
| "Commit/stash your changes before deploying." | |
| ) | |
| if sha != head: | |
| print( | |
| "Explicit historical snapshot selected; local HEAD is not being deployed." | |
| ) | |
| return sha | |
| def archive_snapshot(root, sha, snapshot): | |
| with tempfile.TemporaryFile() as archive: | |
| subprocess.run( | |
| ["git", "archive", "--format=tar", sha], | |
| cwd=root, | |
| stdout=archive, | |
| check=True, | |
| ) | |
| archive.seek(0) | |
| subprocess.run( | |
| ["tar", "-xf", "-", "-C", str(snapshot)], stdin=archive, check=True | |
| ) | |
| (snapshot / "openoperator-build.json").write_text( | |
| json.dumps({"source_sha": sha}) + "\n", encoding="utf-8" | |
| ) | |
| def verify_health(url, sha): | |
| try: | |
| with urllib.request.urlopen(url, timeout=15) as response: | |
| data = json.loads(response.read(16384)) | |
| return response.status == 200 and data.get("sha") == sha | |
| except (OSError, ValueError, AttributeError): | |
| return False | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "--space", default="Leon4gr45/openoperator", help="Target HF Space" | |
| ) | |
| parser.add_argument("--sha", default="", help="Expected GitHub commit SHA") | |
| parser.add_argument( | |
| "--verify", | |
| action="store_true", | |
| help="Require a live /health SHA match after pushing", | |
| ) | |
| parser.add_argument( | |
| "--health-url", default="https://leon4gr45-openoperator.hf.space/health" | |
| ) | |
| args = parser.parse_args() | |
| root = Path(__file__).resolve().parents[1] | |
| try: | |
| sha = deployment_identity(root, args.sha.strip(), args.space) | |
| except (RuntimeError, subprocess.CalledProcessError) as error: | |
| print( | |
| str(error) | |
| if isinstance(error, RuntimeError) | |
| else "Invalid committed deployment SHA", | |
| file=sys.stderr, | |
| ) | |
| return 1 | |
| hf_token = os.environ.get("HF_TOKEN", "").strip() | |
| if not hf_token: | |
| print( | |
| "Error: HF_TOKEN environment variable is required to push to Hugging Face Space.", | |
| file=sys.stderr, | |
| ) | |
| return 1 | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| snapshot = Path(temp_dir) / "hf_snapshot" | |
| snapshot.mkdir(parents=True, exist_ok=True) | |
| archive_snapshot(root, sha, snapshot) | |
| readme_path = snapshot / "README.md" | |
| content = ( | |
| readme_path.read_text(encoding="utf-8") if readme_path.exists() else "" | |
| ) | |
| metadata = """--- | |
| title: OpenOperator | |
| emoji: 🤖 | |
| colorFrom: indigo | |
| colorTo: blue | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| --- | |
| """ | |
| if not content.startswith("---"): | |
| readme_path.write_text(metadata + content, encoding="utf-8") | |
| build_json = snapshot / "openoperator-build.json" | |
| build_json.write_text(json.dumps({"source_sha": sha}) + "\n", encoding="utf-8") | |
| # Create .gitattributes to track large binary extensions via LFS / ignore if needed | |
| gitattributes = snapshot / ".gitattributes" | |
| gitattributes.write_text( | |
| "*.png filter=lfs diff=lfs merge=lfs -text\n*.jpg filter=lfs diff=lfs merge=lfs -text\n*.jpeg filter=lfs diff=lfs merge=lfs -text\n*.gif filter=lfs diff=lfs merge=lfs -text\n*.webm filter=lfs diff=lfs merge=lfs -text\n*.ttf filter=lfs diff=lfs merge=lfs -text\n*.woff filter=lfs diff=lfs merge=lfs -text\n*.woff2 filter=lfs diff=lfs merge=lfs -text\n", | |
| encoding="utf-8", | |
| ) | |
| subprocess.check_call(["git", "init", "--initial-branch=main"], cwd=snapshot) | |
| subprocess.check_call( | |
| ["git", "config", "user.name", "OpenOperator Deployer"], cwd=snapshot | |
| ) | |
| subprocess.check_call( | |
| ["git", "config", "user.email", "deploy@openoperator.local"], cwd=snapshot | |
| ) | |
| # Remove offending documentation and raw binary files (like .png, .jpg, .gif, .webm, .ttf, .woff2) from snapshot | |
| for pattern in ["docs/res", "plugins/_whats_new/webui/assets"]: | |
| path = snapshot / pattern | |
| if path.exists(): | |
| import shutil | |
| shutil.rmtree(path, ignore_errors=True) | |
| for binary_file in snapshot.glob("**/*"): | |
| if binary_file.is_file() and binary_file.suffix in [ | |
| ".png", | |
| ".jpg", | |
| ".jpeg", | |
| ".gif", | |
| ".webm", | |
| ".ttf", | |
| ".woff", | |
| ".woff2", | |
| ]: | |
| try: | |
| binary_file.unlink() | |
| except OSError: | |
| print("Snapshot cleanup failed; refusing upload.", file=sys.stderr) | |
| return 1 | |
| subprocess.check_call(["git", "add", "--all"], cwd=snapshot) | |
| subprocess.check_call( | |
| ["git", "commit", "-m", f"Deploy source revision {sha}"], cwd=snapshot | |
| ) | |
| remote_url = f"https://huggingface.co/spaces/{args.space}" | |
| subprocess.check_call( | |
| ["git", "remote", "add", "huggingface", remote_url], cwd=snapshot | |
| ) | |
| print(f"Pushing snapshot to Hugging Face Space {args.space}...") | |
| askpass = Path(temp_dir) / "askpass.py" | |
| askpass.write_text( | |
| "#!/usr/bin/env python3\nimport os,sys\nprint('oauth2' if 'username' in sys.argv[1].lower() else os.environ['HF_TOKEN'])\n" | |
| ) | |
| askpass.chmod(0o700) | |
| push_env = dict(os.environ, GIT_ASKPASS=str(askpass), GIT_TERMINAL_PROMPT="0") | |
| result = subprocess.run( | |
| ["git", "push", "--force", "huggingface", "main:main"], | |
| cwd=snapshot, | |
| env=push_env, | |
| capture_output=True, | |
| check=False, | |
| ) | |
| if result.returncode: | |
| print( | |
| "HF push failed; credential-bearing transport output withheld.", | |
| file=sys.stderr, | |
| ) | |
| return 1 | |
| print( | |
| f"Snapshot push completed for {args.space} at SHA {sha}; push alone is not deployment qualification." | |
| ) | |
| print(f"Verify {args.health_url}: health SHA must equal {sha}.") | |
| if args.verify: | |
| if not verify_health(args.health_url, sha): | |
| print( | |
| "Deployment unqualified: /health SHA does not match the requested SHA.", | |
| file=sys.stderr, | |
| ) | |
| return 2 | |
| print( | |
| "Deployment qualified: live /health SHA matches requested deployment SHA." | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |