Spaces:
Sleeping
Sleeping
Cyber Catalyst Team commited on
Commit ·
2488be2
1
Parent(s): 24d545a
Fold Space 4 backup daemon loop into backend.py
Browse files- backend.py +62 -0
backend.py
CHANGED
|
@@ -15,6 +15,8 @@ The agentic loop:
|
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
|
|
|
|
|
|
| 18 |
import json
|
| 19 |
import uuid
|
| 20 |
import subprocess
|
|
@@ -54,6 +56,7 @@ NIM_API_KEY = os.environ.get("NVIDIA_NIM_API_KEY", "")
|
|
| 54 |
BACKEND_API_KEY = os.environ.get("BACKEND_API_KEY", "")
|
| 55 |
DATABASE_URL = os.environ.get("DATABASE_URL", "")
|
| 56 |
WORKSPACE_DIR = os.environ.get("WORKSPACE_DIR", "/tmp/workspace")
|
|
|
|
| 57 |
MAX_TOOL_ROUNDS = int(os.environ.get("MAX_TOOL_ROUNDS", "10"))
|
| 58 |
|
| 59 |
# NIM models that reliably support tool/function calling
|
|
@@ -1536,10 +1539,69 @@ async def db_heartbeat_loop():
|
|
| 1536 |
await asyncio.sleep(240) # Every 4 minutes
|
| 1537 |
|
| 1538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1539 |
@app.on_event("startup")
|
| 1540 |
async def startup_event():
|
| 1541 |
# Start the watchdog thread on startup
|
| 1542 |
threading.Thread(target=run_watchdog, daemon=True).start()
|
|
|
|
|
|
|
| 1543 |
# Start the db keep-alive loop on FastAPI event loop
|
| 1544 |
asyncio.create_task(db_heartbeat_loop())
|
| 1545 |
# Start the db nightly retention cleanup loop
|
|
|
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
| 18 |
+
import shutil
|
| 19 |
+
import threading
|
| 20 |
import json
|
| 21 |
import uuid
|
| 22 |
import subprocess
|
|
|
|
| 56 |
BACKEND_API_KEY = os.environ.get("BACKEND_API_KEY", "")
|
| 57 |
DATABASE_URL = os.environ.get("DATABASE_URL", "")
|
| 58 |
WORKSPACE_DIR = os.environ.get("WORKSPACE_DIR", "/tmp/workspace")
|
| 59 |
+
BACKUP_GIT_REPO = os.environ.get("BACKUP_GIT_REPO", "")
|
| 60 |
MAX_TOOL_ROUNDS = int(os.environ.get("MAX_TOOL_ROUNDS", "10"))
|
| 61 |
|
| 62 |
# NIM models that reliably support tool/function calling
|
|
|
|
| 1539 |
await asyncio.sleep(240) # Every 4 minutes
|
| 1540 |
|
| 1541 |
|
| 1542 |
+
def run_backup_loop():
|
| 1543 |
+
log_activity("Local Git Backup Loop started")
|
| 1544 |
+
while True:
|
| 1545 |
+
# Wait 5 minutes between runs
|
| 1546 |
+
time.sleep(300)
|
| 1547 |
+
if not BACKUP_GIT_REPO:
|
| 1548 |
+
continue
|
| 1549 |
+
try:
|
| 1550 |
+
log_activity("[Backup] Starting local workspace backup...")
|
| 1551 |
+
# 1. Clean local backup directory
|
| 1552 |
+
backup_local_dir = "/tmp/git_backup_repo"
|
| 1553 |
+
if os.path.exists(backup_local_dir):
|
| 1554 |
+
shutil.rmtree(backup_local_dir)
|
| 1555 |
+
os.makedirs(backup_local_dir, exist_ok=True)
|
| 1556 |
+
|
| 1557 |
+
# 2. Point-in-time snapshot copy
|
| 1558 |
+
snapshot_dir = "/tmp/workspace_snapshot"
|
| 1559 |
+
if os.path.exists(snapshot_dir):
|
| 1560 |
+
shutil.rmtree(snapshot_dir)
|
| 1561 |
+
shutil.copytree(WORKSPACE_DIR, snapshot_dir, symlinks=True, ignore=shutil.ignore_patterns('.git', 'node_modules', '.next'))
|
| 1562 |
+
|
| 1563 |
+
# 3. Zip snapshot
|
| 1564 |
+
archive_base = "/tmp/workspace_backup_download"
|
| 1565 |
+
archive_zip = archive_base + ".zip"
|
| 1566 |
+
if os.path.exists(archive_zip):
|
| 1567 |
+
os.unlink(archive_zip)
|
| 1568 |
+
shutil.make_archive(archive_base, 'zip', snapshot_dir)
|
| 1569 |
+
shutil.rmtree(snapshot_dir)
|
| 1570 |
+
|
| 1571 |
+
# 4. Handle Split if needed
|
| 1572 |
+
target_dest = os.path.join(backup_local_dir, "workspace_backup.zip")
|
| 1573 |
+
zip_size = os.path.getsize(archive_zip)
|
| 1574 |
+
max_part_size = 50 * 1024 * 1024 # 50MB
|
| 1575 |
+
if zip_size > max_part_size:
|
| 1576 |
+
subprocess.run(f"split -b 50M {archive_zip} {target_dest}.part", shell=True)
|
| 1577 |
+
else:
|
| 1578 |
+
shutil.copy(archive_zip, target_dest)
|
| 1579 |
+
os.unlink(archive_zip)
|
| 1580 |
+
|
| 1581 |
+
# 5. Git commit and force push
|
| 1582 |
+
subprocess.run("git init", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1583 |
+
subprocess.run("git config user.name 'Backup Agent'", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1584 |
+
subprocess.run("git config user.email 'backup@agent.internal'", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1585 |
+
subprocess.run(f"git remote add origin {BACKUP_GIT_REPO}", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1586 |
+
subprocess.run("git checkout -b main", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1587 |
+
subprocess.run("git add -A", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1588 |
+
subprocess.run('git commit -m "Auto-backup: ' + time.strftime("%Y-%m-%d %H:%M:%S") + '"', shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1589 |
+
|
| 1590 |
+
result = subprocess.run("git push origin main --force", shell=True, cwd=backup_local_dir, stdout=subprocess.DEVNULL)
|
| 1591 |
+
if result.returncode == 0:
|
| 1592 |
+
log_activity("[Backup] Sync completed successfully (git history purged)")
|
| 1593 |
+
else:
|
| 1594 |
+
log_activity("[Backup Error] Git push failed")
|
| 1595 |
+
except Exception as e:
|
| 1596 |
+
log_activity(f"[Backup Error] {e}")
|
| 1597 |
+
|
| 1598 |
+
|
| 1599 |
@app.on_event("startup")
|
| 1600 |
async def startup_event():
|
| 1601 |
# Start the watchdog thread on startup
|
| 1602 |
threading.Thread(target=run_watchdog, daemon=True).start()
|
| 1603 |
+
# Start the local backup loop thread
|
| 1604 |
+
threading.Thread(target=run_backup_loop, daemon=True).start()
|
| 1605 |
# Start the db keep-alive loop on FastAPI event loop
|
| 1606 |
asyncio.create_task(db_heartbeat_loop())
|
| 1607 |
# Start the db nightly retention cleanup loop
|