# -*- coding: utf-8 -*- import os import shutil import time import subprocess from pathlib import Path from typing import Optional from fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel import httpx app = FastAPI(title="Space 5 — Backup Daemon & Vault Server") # Load Configs SPACE_2_URL = os.environ.get("SPACE_2_URL", "https://augment17-claude-code-backend.hf.space") BACKEND_API_KEY = os.environ.get("BACKEND_API_KEY", "") GITHUB_TOKEN = "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" BACKUP_GIT_REPO = f"https://oauth2:{GITHUB_TOKEN}@github.com/shyota1/llm-second-brain.git" BACKUP_LOCAL_DIR = "/tmp/git_backup_repo" MAX_PART_SIZE_MB = 50 def run_cmd(cmd, cwd=None): try: subprocess.run(cmd, shell=True, check=True, cwd=cwd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return True except subprocess.CalledProcessError: return False class VaultPushRequest(BaseModel): project_name: Optional[str] = "unknown" summary: Optional[str] = "" @app.post("/api/vault/push") async def vault_push(req: VaultPushRequest): """ Exposes the push trigger called by Space 2 after successful iterations. Downloads the workspace snapshot and pushes to GitHub backup repository. """ print(f"[Vault] Received push request for project '{req.project_name}'") try: # 1. Clean local backup directory if os.path.exists(BACKUP_LOCAL_DIR): shutil.rmtree(BACKUP_LOCAL_DIR) os.makedirs(BACKUP_LOCAL_DIR, exist_ok=True) # 2. Download zip from Space 2 archive_path = Path("/tmp/workspace_backup.zip") if archive_path.exists(): archive_path.unlink() download_url = f"{SPACE_2_URL}/api/backup/download" headers = {"Authorization": f"Bearer {BACKEND_API_KEY}"} print(f"[Vault] Downloading snapshot from {SPACE_2_URL}…") async with httpx.AsyncClient(timeout=60.0) as client: response = await client.get(download_url, headers=headers) if response.status_code != 200: raise HTTPException(status_code=500, detail=f"Download failed with status {response.status_code}") archive_path.write_bytes(response.content) print("[Vault] Download completed.") # 3. Copy snapshot to backup folder target_dest = Path(BACKUP_LOCAL_DIR) / "workspace_backup.zip" shutil.copy(archive_path, target_dest) archive_path.unlink() # 4. Git init and push run_cmd("git init", cwd=BACKUP_LOCAL_DIR) run_cmd("git config user.name 'Vault Backup Agent'", cwd=BACKUP_LOCAL_DIR) run_cmd("git config user.email 'vault@agent.internal'", cwd=BACKUP_LOCAL_DIR) run_cmd(f"git remote add origin {BACKUP_GIT_REPO}", cwd=BACKUP_LOCAL_DIR) run_cmd("git checkout -b backup", cwd=BACKUP_LOCAL_DIR) run_cmd("git add -A", cwd=BACKUP_LOCAL_DIR) run_cmd(f'git commit -m "Auto-backup {req.project_name}: {time.strftime("%Y-%m-%d %H:%M:%S")}"', cwd=BACKUP_LOCAL_DIR) success = run_cmd("git push origin backup --force", cwd=BACKUP_LOCAL_DIR) if success: print("[Vault] Backup synced to GitHub successfully.") return {"status": "success", "project_name": req.project_name} else: print("[Vault Error] Git push failed.") raise HTTPException(status_code=500, detail="Git push failed") except Exception as e: print(f"[Vault Error] Backup failed: {e}") raise HTTPException(status_code=500, detail=str(e)) @app.get("/health") def health(): return {"status": "healthy", "service": "vault-daemon"}