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)