Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| import argparse | |
| import shutil | |
| import sys | |
| import time | |
| from pathlib import Path | |
| DEFAULT_REPO_ID = "shazhass/bacpilot-backend" | |
| DEFAULT_DEPLOY_DIR = Path("/tmp/bacpilot_hf_deploy") | |
| PATHS_TO_COPY = ( | |
| "app", | |
| "Dockerfile", | |
| "README.md", | |
| "requirements.txt", | |
| "pyproject.toml", | |
| "Procfile", | |
| "runtime.txt", | |
| ) | |
| SECRET_PATH_PATTERNS = ( | |
| ".env", | |
| ".env.", | |
| ".secrets", | |
| "quality/private", | |
| ".git", | |
| ".venv", | |
| "__pycache__", | |
| ) | |
| SECRET_CONTENT_PATTERNS = ( | |
| "OPENROUTER_API_KEY=", | |
| "SUPABASE_SERVICE_ROLE_KEY=", | |
| "INTERNAL_GATEWAY_SECRET=", | |
| "HF_TOKEN=", | |
| "HUGGINGFACE_TOKEN=", | |
| ) | |
| def build_deploy_folder(source_root: Path, deploy_root: Path) -> list[str]: | |
| if deploy_root.exists(): | |
| shutil.rmtree(deploy_root) | |
| deploy_root.mkdir(parents=True) | |
| copied: list[str] = [] | |
| for relative_path in PATHS_TO_COPY: | |
| src = source_root / relative_path | |
| dst = deploy_root / relative_path | |
| if not src.exists(): | |
| print(f"skip_missing={relative_path}") | |
| continue | |
| if src.is_dir(): | |
| shutil.copytree( | |
| src, | |
| dst, | |
| ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".pytest_cache"), | |
| ) | |
| else: | |
| shutil.copy2(src, dst) | |
| copied.append(relative_path) | |
| return copied | |
| def scan_for_sensitive_paths(deploy_root: Path) -> list[str]: | |
| findings: list[str] = [] | |
| for path in deploy_root.rglob("*"): | |
| relative = path.relative_to(deploy_root).as_posix() | |
| lower_relative = relative.lower() | |
| for pattern in SECRET_PATH_PATTERNS: | |
| if pattern.lower() in lower_relative: | |
| findings.append(relative) | |
| break | |
| return sorted(set(findings)) | |
| def scan_for_sensitive_contents(deploy_root: Path) -> list[str]: | |
| findings: list[str] = [] | |
| for path in deploy_root.rglob("*"): | |
| if not path.is_file(): | |
| continue | |
| relative = path.relative_to(deploy_root).as_posix() | |
| try: | |
| text = path.read_text(encoding="utf-8") | |
| except UnicodeDecodeError: | |
| continue | |
| for pattern in SECRET_CONTENT_PATTERNS: | |
| if pattern in text: | |
| findings.append(relative) | |
| break | |
| return sorted(set(findings)) | |
| def upload_to_hugging_face(repo_id: str, deploy_root: Path, commit_message: str) -> None: | |
| try: | |
| from huggingface_hub import HfApi, get_token | |
| except ImportError as exc: | |
| raise RuntimeError( | |
| "huggingface_hub manquant. Installe-le avec : " | |
| "python -m pip install 'huggingface_hub>=1.21.0'" | |
| ) from exc | |
| token = get_token() | |
| if not token: | |
| raise RuntimeError("HF_TOKEN_LOCAL=MISSING. Lance : hf auth login") | |
| api = HfApi(token=token) | |
| api.upload_folder( | |
| repo_id=repo_id, | |
| repo_type="space", | |
| folder_path=str(deploy_root), | |
| path_in_repo=".", | |
| commit_message=commit_message, | |
| ignore_patterns=[ | |
| ".git/*", | |
| ".env", | |
| ".env.*", | |
| ".venv/*", | |
| ".secrets/*", | |
| "quality/private/*", | |
| "__pycache__/*", | |
| "*.pyc", | |
| ], | |
| ) | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description="Déployer le backend BacPilot vers Hugging Face Space via API." | |
| ) | |
| parser.add_argument("--repo-id", default=DEFAULT_REPO_ID) | |
| parser.add_argument("--deploy-dir", default=str(DEFAULT_DEPLOY_DIR)) | |
| parser.add_argument("--commit-message", default="Deploy BacPilot backend via API") | |
| parser.add_argument("--dry-run", action="store_true") | |
| parser.add_argument("--wait-seconds", type=int, default=90) | |
| return parser.parse_args() | |
| def main() -> int: | |
| args = parse_args() | |
| source_root = Path.cwd() | |
| deploy_root = Path(args.deploy_dir) | |
| copied = build_deploy_folder(source_root=source_root, deploy_root=deploy_root) | |
| path_findings = scan_for_sensitive_paths(deploy_root) | |
| content_findings = scan_for_sensitive_contents(deploy_root) | |
| print(f"HF_DEPLOY_FOLDER={deploy_root}") | |
| print("HF_DEPLOY_COPIED=" + ",".join(copied)) | |
| if path_findings or content_findings: | |
| print("HF_DEPLOY_SECRET_SCAN=FAILED") | |
| for finding in path_findings: | |
| print(f"sensitive_path={finding}") | |
| for finding in content_findings: | |
| print(f"sensitive_content_file={finding}") | |
| return 1 | |
| print("HF_DEPLOY_SECRET_SCAN=OK") | |
| if args.dry_run: | |
| print("HF_UPLOAD_FOLDER=SKIPPED_DRY_RUN") | |
| return 0 | |
| upload_to_hugging_face( | |
| repo_id=args.repo_id, | |
| deploy_root=deploy_root, | |
| commit_message=args.commit_message, | |
| ) | |
| print("HF_UPLOAD_FOLDER=OK") | |
| print(f"HF_COMMIT_MESSAGE={args.commit_message}") | |
| if args.wait_seconds > 0: | |
| print(f"HF_REBUILD_WAIT_SECONDS={args.wait_seconds}") | |
| time.sleep(args.wait_seconds) | |
| return 0 | |
| if __name__ == "__main__": | |
| try: | |
| raise SystemExit(main()) | |
| except Exception as exc: | |
| print(f"HF_DEPLOY_ERROR={exc}", file=sys.stderr) | |
| raise SystemExit(1) | |