Backup Agent
feat: turn backup daemon into FastAPI vault server with HTTPS PAT git remote
5a5ae07
Raw
History Blame Contribute Delete
3.74 kB
# -*- 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"}