Upload 20 files
Browse files- routers/backup.py +11 -74
routers/backup.py
CHANGED
|
@@ -10,12 +10,10 @@ Requires env var:
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
import os
|
| 13 |
-
import socket
|
| 14 |
import shutil
|
| 15 |
import tarfile
|
| 16 |
from datetime import datetime
|
| 17 |
from pathlib import Path
|
| 18 |
-
from urllib.parse import urlparse
|
| 19 |
|
| 20 |
import httpx
|
| 21 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
|
@@ -25,76 +23,15 @@ from storage import load_meta, save_meta, validate_zone_name
|
|
| 25 |
|
| 26 |
router = APIRouter(prefix="/api/backup", tags=["backup"])
|
| 27 |
|
| 28 |
-
# ── Resolve Worker IP at startup to avoid DNS issues in HF Spaces ──
|
| 29 |
-
_worker_ip: str | None = None
|
| 30 |
|
| 31 |
-
def
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
if _worker_ip:
|
| 35 |
-
return _worker_ip
|
| 36 |
-
if not ADMIN_API_URL:
|
| 37 |
-
return None
|
| 38 |
-
hostname = urlparse(ADMIN_API_URL).hostname
|
| 39 |
-
if not hostname:
|
| 40 |
-
return None
|
| 41 |
-
# Try system DNS first
|
| 42 |
-
try:
|
| 43 |
-
_worker_ip = socket.getaddrinfo(hostname, 443, socket.AF_INET)[0][4][0]
|
| 44 |
-
return _worker_ip
|
| 45 |
-
except socket.gaierror:
|
| 46 |
-
pass
|
| 47 |
-
# Fallback: query Cloudflare DNS over HTTPS
|
| 48 |
-
try:
|
| 49 |
-
import json
|
| 50 |
-
from urllib.request import urlopen, Request as UrlRequest
|
| 51 |
-
req = UrlRequest(
|
| 52 |
-
f"https://1.1.1.1/dns-query?name={hostname}&type=A",
|
| 53 |
-
headers={"Accept": "application/dns-json"},
|
| 54 |
-
)
|
| 55 |
-
with urlopen(req, timeout=5) as resp:
|
| 56 |
-
data = json.loads(resp.read())
|
| 57 |
-
for ans in data.get("Answer", []):
|
| 58 |
-
if ans.get("type") == 1: # A record
|
| 59 |
-
_worker_ip = ans["data"]
|
| 60 |
-
return _worker_ip
|
| 61 |
-
except Exception:
|
| 62 |
-
pass
|
| 63 |
-
return None
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def _make_client(timeout: int = 30) -> httpx.AsyncClient:
|
| 67 |
-
"""Create an httpx AsyncClient."""
|
| 68 |
-
return httpx.AsyncClient(timeout=timeout)
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
def _make_sync_client(timeout: int = 300) -> httpx.Client:
|
| 72 |
-
"""Create a sync httpx Client."""
|
| 73 |
-
return httpx.Client(timeout=timeout)
|
| 74 |
|
| 75 |
|
| 76 |
def _url(path: str) -> str:
|
| 77 |
-
"""Build the full Worker URL for a given path
|
| 78 |
-
|
| 79 |
-
ip = _resolve_worker_ip()
|
| 80 |
-
if ip:
|
| 81 |
-
hostname = urlparse(ADMIN_API_URL).hostname or ""
|
| 82 |
-
return full.replace(hostname, ip)
|
| 83 |
-
return full
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
def _host_header() -> dict:
|
| 87 |
-
"""Return Host header if using IP-resolved URL."""
|
| 88 |
-
ip = _resolve_worker_ip()
|
| 89 |
-
if ip:
|
| 90 |
-
hostname = urlparse(ADMIN_API_URL).hostname or ""
|
| 91 |
-
return {"Host": hostname}
|
| 92 |
-
return {}
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
def _worker_headers(token: str) -> dict:
|
| 96 |
-
"""Build headers for Worker API calls."""
|
| 97 |
-
return {"Authorization": f"Bearer {token}", **_host_header()}
|
| 98 |
|
| 99 |
|
| 100 |
def _get_token(request: Request) -> str:
|
|
@@ -140,7 +77,7 @@ async def list_backups(request: Request):
|
|
| 140 |
if not token:
|
| 141 |
raise HTTPException(401, "Chua dang nhap")
|
| 142 |
try:
|
| 143 |
-
async with
|
| 144 |
resp = await client.get(_url("/backup/list"), headers=_worker_headers(token))
|
| 145 |
if resp.status_code != 200:
|
| 146 |
data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {"error": resp.text}
|
|
@@ -173,7 +110,7 @@ async def backup_zone(zone_name: str, request: Request, background_tasks: Backgr
|
|
| 173 |
try:
|
| 174 |
archive_path = _create_zone_archive(zone_name)
|
| 175 |
try:
|
| 176 |
-
with
|
| 177 |
with open(archive_path, "rb") as f:
|
| 178 |
resp = client.post(
|
| 179 |
_url(f"/backup/upload/{zone_name}"),
|
|
@@ -221,7 +158,7 @@ async def backup_all(request: Request, background_tasks: BackgroundTasks):
|
|
| 221 |
_backup_status["progress"] = f"Dang backup zone {zone_name} ({done + 1}/{total})..."
|
| 222 |
archive_path = _create_zone_archive(zone_name)
|
| 223 |
try:
|
| 224 |
-
with
|
| 225 |
with open(archive_path, "rb") as f:
|
| 226 |
resp = client.post(
|
| 227 |
_url(f"/backup/upload/{zone_name}"),
|
|
@@ -264,7 +201,7 @@ async def restore_zone(zone_name: str, request: Request, background_tasks: Backg
|
|
| 264 |
_backup_status["error"] = None
|
| 265 |
_backup_status["progress"] = f"Dang restore zone: {zone_name}..."
|
| 266 |
try:
|
| 267 |
-
with
|
| 268 |
resp = client.get(_url(f"/backup/download/{zone_name}"), headers=_worker_headers(token))
|
| 269 |
if resp.status_code == 404:
|
| 270 |
raise ValueError(f"Backup zone '{zone_name}' khong ton tai")
|
|
@@ -319,7 +256,7 @@ async def restore_all(request: Request, background_tasks: BackgroundTasks):
|
|
| 319 |
_backup_status["error"] = None
|
| 320 |
_backup_status["progress"] = "Dang restore tat ca zones..."
|
| 321 |
try:
|
| 322 |
-
with
|
| 323 |
resp = client.get(_url("/backup/list"), headers=_worker_headers(token))
|
| 324 |
if resp.status_code != 200:
|
| 325 |
raise ValueError(f"Khong the lay danh sach backup: {resp.text}")
|
|
@@ -330,7 +267,7 @@ async def restore_all(request: Request, background_tasks: BackgroundTasks):
|
|
| 330 |
for b in backup_list:
|
| 331 |
zone_name = b["zone_name"]
|
| 332 |
_backup_status["progress"] = f"Dang restore zone {zone_name} ({done + 1}/{total})..."
|
| 333 |
-
with
|
| 334 |
resp = client.get(_url(f"/backup/download/{zone_name}"), headers=_worker_headers(token))
|
| 335 |
if resp.status_code != 200:
|
| 336 |
continue
|
|
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
import os
|
|
|
|
| 13 |
import shutil
|
| 14 |
import tarfile
|
| 15 |
from datetime import datetime
|
| 16 |
from pathlib import Path
|
|
|
|
| 17 |
|
| 18 |
import httpx
|
| 19 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
|
|
|
| 23 |
|
| 24 |
router = APIRouter(prefix="/api/backup", tags=["backup"])
|
| 25 |
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
def _worker_headers(token: str) -> dict:
|
| 28 |
+
"""Build headers for Worker API calls."""
|
| 29 |
+
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def _url(path: str) -> str:
|
| 33 |
+
"""Build the full Worker URL for a given path."""
|
| 34 |
+
return f"{ADMIN_API_URL}{path}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
def _get_token(request: Request) -> str:
|
|
|
|
| 77 |
if not token:
|
| 78 |
raise HTTPException(401, "Chua dang nhap")
|
| 79 |
try:
|
| 80 |
+
async with httpx.AsyncClient(timeout=30) as client:
|
| 81 |
resp = await client.get(_url("/backup/list"), headers=_worker_headers(token))
|
| 82 |
if resp.status_code != 200:
|
| 83 |
data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {"error": resp.text}
|
|
|
|
| 110 |
try:
|
| 111 |
archive_path = _create_zone_archive(zone_name)
|
| 112 |
try:
|
| 113 |
+
with httpx.Client(timeout=300) as client:
|
| 114 |
with open(archive_path, "rb") as f:
|
| 115 |
resp = client.post(
|
| 116 |
_url(f"/backup/upload/{zone_name}"),
|
|
|
|
| 158 |
_backup_status["progress"] = f"Dang backup zone {zone_name} ({done + 1}/{total})..."
|
| 159 |
archive_path = _create_zone_archive(zone_name)
|
| 160 |
try:
|
| 161 |
+
with httpx.Client(timeout=300) as client:
|
| 162 |
with open(archive_path, "rb") as f:
|
| 163 |
resp = client.post(
|
| 164 |
_url(f"/backup/upload/{zone_name}"),
|
|
|
|
| 201 |
_backup_status["error"] = None
|
| 202 |
_backup_status["progress"] = f"Dang restore zone: {zone_name}..."
|
| 203 |
try:
|
| 204 |
+
with httpx.Client(timeout=300) as client:
|
| 205 |
resp = client.get(_url(f"/backup/download/{zone_name}"), headers=_worker_headers(token))
|
| 206 |
if resp.status_code == 404:
|
| 207 |
raise ValueError(f"Backup zone '{zone_name}' khong ton tai")
|
|
|
|
| 256 |
_backup_status["error"] = None
|
| 257 |
_backup_status["progress"] = "Dang restore tat ca zones..."
|
| 258 |
try:
|
| 259 |
+
with httpx.Client(timeout=30) as client:
|
| 260 |
resp = client.get(_url("/backup/list"), headers=_worker_headers(token))
|
| 261 |
if resp.status_code != 200:
|
| 262 |
raise ValueError(f"Khong the lay danh sach backup: {resp.text}")
|
|
|
|
| 267 |
for b in backup_list:
|
| 268 |
zone_name = b["zone_name"]
|
| 269 |
_backup_status["progress"] = f"Dang restore zone {zone_name} ({done + 1}/{total})..."
|
| 270 |
+
with httpx.Client(timeout=300) as client:
|
| 271 |
resp = client.get(_url(f"/backup/download/{zone_name}"), headers=_worker_headers(token))
|
| 272 |
if resp.status_code != 200:
|
| 273 |
continue
|