Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Deploy the tested GitHub source snapshot as the organization's Docker Space.""" | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import os | |
| from pathlib import Path | |
| import shutil | |
| import subprocess | |
| import tempfile | |
| ROOT = Path(__file__).resolve().parents[1] | |
| def stage_cloud(destination: Path, revision: str, *, include_seed=True): | |
| fixed = ["pyproject.toml", "LICENSE.txt", "space/app.py", "space/requirements.txt", "space/constraints.txt", | |
| "space/vendor/ts_bench/collect_weekly_data.py", "space/vendor/ts_bench/export_model_inputs.py", | |
| "bootstrap/manifest.json"] | |
| if include_seed: | |
| fixed.append("bootstrap/seed.tar.gz") | |
| files = [ROOT / name for name in fixed] | |
| for name in ("cloud", "scripts", "src", "configs", "space/src", "space/user_models"): | |
| files.extend(p for p in (ROOT / name).rglob("*") if p.is_file() | |
| and p.suffix in {".py", ".json", ".yaml", ".yml", ".txt", ".md"}) | |
| secrets = [v.encode() for k,v in os.environ.items() if v and len(v)>=12 | |
| and any(word in k for word in ("TOKEN", "API_KEY", "PASSWORD", "SECRET"))] | |
| for source in sorted(set(files)): | |
| rel = source.relative_to(ROOT) | |
| if any(part.startswith(".env") for part in rel.parts): | |
| raise ValueError("Environment files must never enter the Space") | |
| content = source.read_bytes() | |
| if any(secret in content for secret in secrets): | |
| raise ValueError(f"Configured credential detected in deployment file {rel}") | |
| path = destination / rel | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_bytes(content) | |
| shutil.copy2(ROOT / "cloud/Dockerfile", destination / "Dockerfile") | |
| shutil.copy2(ROOT / "cloud/README.md", destination / "README.md") | |
| (destination / "deployment.json").write_text(json.dumps({ | |
| "source_repository":"https://github.com/Thinkcat-Lab/LiveHouse-TS", "source_branch":"main", | |
| "source_revision":revision, | |
| }, indent=2) + "\n") | |
| def publish_bootstrap(api, state_repo): | |
| if not api.dataset_info(state_repo).private: | |
| raise ValueError("Bootstrap storage must use the private operator Dataset") | |
| expected = json.loads((ROOT / "bootstrap/manifest.json").read_text())["sha256"] | |
| archive = ROOT / "bootstrap/seed.tar.gz" | |
| if hashlib.sha256(archive.read_bytes()).hexdigest() != expected: | |
| raise ValueError("Local bootstrap checksum mismatch") | |
| remote_path = f"bootstrap/{expected}.tar.gz" | |
| if not api.file_exists(state_repo, remote_path, repo_type="dataset"): | |
| api.upload_file(path_or_fileobj=archive, path_in_repo=remote_path, | |
| repo_id=state_repo, repo_type="dataset", | |
| commit_message="Store immutable bootstrap for hosted deployment") | |
| def main(): | |
| from dotenv import load_dotenv | |
| from huggingface_hub import HfApi | |
| load_dotenv(ROOT / ".env") | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--repo", default=os.getenv("HF_SPACE_REPO", "ThinkcatLab/LiveHouse-TS")) | |
| parser.add_argument("--dry-run", action="store_true") | |
| parser.add_argument("--output-dir", type=Path, help="Keep a validated build context; requires --dry-run") | |
| args = parser.parse_args() | |
| if args.output_dir and not args.dry_run: | |
| parser.error("--output-dir requires --dry-run") | |
| revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() | |
| with tempfile.TemporaryDirectory(prefix="livehouse-cloud-stage-") as temp: | |
| stage = Path(temp) | |
| stage_cloud(stage, revision, include_seed=args.dry_run) | |
| print(f"Validated cloud deployment from {revision}: {sum(p.is_file() for p in stage.rglob('*'))} files") | |
| if args.dry_run: | |
| if args.output_dir: | |
| if args.output_dir.exists(): | |
| raise SystemExit("Build context destination already exists") | |
| shutil.copytree(stage, args.output_dir) | |
| return | |
| if not os.getenv("HF_TOKEN"): | |
| raise SystemExit("HF_TOKEN required") | |
| api = HfApi(token=os.environ["HF_TOKEN"]) | |
| publish_bootstrap(api, os.getenv("HF_STATE_REPO", "ThinkcatLab/LiveHouse-TS-state")) | |
| # A changed README SDK converts the transferred Space to Docker while | |
| # preserving its Git history. No hardware purchase is performed. | |
| api.repo_info(args.repo, repo_type="space") | |
| # A transferred Space may contain legacy top-level packages that shadow | |
| # src/. Replace the deployment tree while retaining all Git history. | |
| commit = api.upload_folder(folder_path=stage, repo_id=args.repo, repo_type="space", | |
| delete_patterns=["*"], | |
| commit_message=f"Deploy GitHub main {revision[:12]}") | |
| print(f"Deployed {commit.oid}: https://huggingface.co/spaces/{args.repo}") | |
| if __name__ == "__main__": | |
| main() | |