#!/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()