new changes

This commit is contained in:
Niranjan
2026-04-07 13:23:35 +05:30
parent df015e4d5a
commit 6dea3b4307
38 changed files with 1332 additions and 119 deletions

View File

@@ -27,6 +27,9 @@ class CreateBackupPlanRequest(BaseModel):
target_id: int
schedule: str # cron, e.g. "0 2 * * *"
enabled: bool = True
s3_bucket: str = ""
s3_endpoint: str = ""
s3_key_prefix: str = ""
@router.get("/plans")
@@ -45,6 +48,9 @@ async def backup_plans_list(
"target_id": r.target_id,
"schedule": r.schedule,
"enabled": r.enabled,
"s3_bucket": getattr(r, "s3_bucket", None) or "",
"s3_endpoint": getattr(r, "s3_endpoint", None) or "",
"s3_key_prefix": getattr(r, "s3_key_prefix", None) or "",
}
for r in rows
]
@@ -79,6 +85,9 @@ async def backup_plan_create(
target_id=body.target_id,
schedule=body.schedule,
enabled=body.enabled,
s3_bucket=(body.s3_bucket or "")[:256],
s3_endpoint=(body.s3_endpoint or "")[:512],
s3_key_prefix=(body.s3_key_prefix or "")[:256],
)
db.add(plan)
await db.commit()
@@ -107,6 +116,9 @@ async def backup_plan_update(
plan.target_id = body.target_id
plan.schedule = body.schedule
plan.enabled = body.enabled
plan.s3_bucket = (body.s3_bucket or "")[:256]
plan.s3_endpoint = (body.s3_endpoint or "")[:512]
plan.s3_key_prefix = (body.s3_key_prefix or "")[:256]
await db.commit()
return {"status": True, "msg": "Updated"}
@@ -143,6 +155,27 @@ def _run_site_backup(site: Site) -> tuple[bool, str, str | None]:
return False, str(e), None
def _maybe_upload_s3(local_file: str, plan: BackupPlan) -> tuple[bool, str]:
"""Copy backup file to S3-compatible bucket if plan.s3_bucket set. Uses AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY."""
bucket = (getattr(plan, "s3_bucket", None) or "").strip()
if not bucket or not os.path.isfile(local_file):
return True, ""
try:
import boto3
except ImportError:
return False, "boto3 not installed (pip install boto3)"
ep = (getattr(plan, "s3_endpoint", None) or "").strip() or None
prefix = (getattr(plan, "s3_key_prefix", None) or "").strip().strip("/")
key_base = os.path.basename(local_file)
key = f"{prefix}/{key_base}" if prefix else key_base
try:
client = boto3.client("s3", endpoint_url=ep)
client.upload_file(local_file, bucket, key)
return True, f"s3://{bucket}/{key}"
except Exception as e:
return False, str(e)
def _run_database_backup(dbo: Database) -> tuple[bool, str, str | None]:
"""Run database backup (sync). Returns (ok, msg, filename)."""
cfg = get_runtime_config()
@@ -164,15 +197,17 @@ async def backup_run_scheduled(
"""Run all due backup plans. Call this from cron (e.g. every hour) or manually."""
from datetime import datetime as dt
now = dt.utcnow()
cfg = get_runtime_config()
result = await db.execute(select(BackupPlan).where(BackupPlan.enabled == True))
plans = result.scalars().all()
results = []
for plan in plans:
ok = False
msg = ""
try:
prev_run = croniter(plan.schedule, now).get_prev(dt)
# Run if we're within 15 minutes after the scheduled time
secs_since = (now - prev_run).total_seconds()
if secs_since > 900 or secs_since < 0: # Not within 15 min window
if secs_since > 900 or secs_since < 0:
continue
except Exception:
continue
@@ -183,6 +218,11 @@ async def backup_run_scheduled(
results.append({"plan": plan.name, "status": "skipped", "msg": "Site not found or path invalid"})
continue
ok, msg, filename = _run_site_backup(site)
if ok and filename:
full = os.path.join(cfg["backup_path"], filename)
u_ok, u_msg = _maybe_upload_s3(full, plan)
if u_msg:
msg = f"{msg}; {u_msg}" if u_ok else f"{msg}; S3 failed: {u_msg}"
if ok:
send_email(
subject=f"YakPanel - Scheduled backup: {plan.name}",
@@ -195,6 +235,11 @@ async def backup_run_scheduled(
results.append({"plan": plan.name, "status": "skipped", "msg": "Database not found"})
continue
ok, msg, filename = _run_database_backup(dbo)
if ok and filename:
full = os.path.join(cfg["backup_path"], "database", filename)
u_ok, u_msg = _maybe_upload_s3(full, plan)
if u_msg:
msg = f"{msg}; {u_msg}" if u_ok else f"{msg}; S3 failed: {u_msg}"
if ok:
send_email(
subject=f"YakPanel - Scheduled backup: {plan.name}",