kokokoasd commited on
Commit
19757a2
·
verified ·
1 Parent(s): 72688d6

Upload 20 files

Browse files
Files changed (2) hide show
  1. Dockerfile +5 -3
  2. routers/backup.py +86 -18
Dockerfile CHANGED
@@ -1,9 +1,11 @@
1
  FROM python:3.11-slim
2
 
3
- # Cài system dependencies
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
5
- curl wget git build-essential procps htop nano vim \
6
- && rm -rf /var/lib/apt/lists/*
 
 
7
 
8
  # Cài Node.js 20
9
  RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
 
1
  FROM python:3.11-slim
2
 
3
+ # Cài system dependencies + thêm DNS fallback
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
5
+ curl wget git build-essential procps htop nano vim dnsutils \
6
+ && rm -rf /var/lib/apt/lists/* \
7
+ && echo "nameserver 1.1.1.1" >> /etc/resolv.conf \
8
+ && echo "nameserver 8.8.8.8" >> /etc/resolv.conf
9
 
10
  # Cài Node.js 20
11
  RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
routers/backup.py CHANGED
@@ -10,10 +10,12 @@ Requires env var:
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,6 +25,77 @@ from storage import load_meta, save_meta, validate_zone_name
23
 
24
  router = APIRouter(prefix="/api/backup", tags=["backup"])
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  def _get_token(request: Request) -> str:
28
  """Extract JWT token from request Authorization header."""
@@ -32,11 +105,6 @@ def _get_token(request: Request) -> str:
32
  return ""
33
 
34
 
35
- def _worker_headers(token: str) -> dict:
36
- """Build headers for Worker API calls."""
37
- return {"Authorization": f"Bearer {token}"}
38
-
39
-
40
  def _create_zone_archive(zone_name: str) -> Path:
41
  """Create a tar.gz archive of a zone directory."""
42
  zone_path = DATA_DIR / zone_name
@@ -72,8 +140,8 @@ async def list_backups(request: Request):
72
  if not token:
73
  raise HTTPException(401, "Chua dang nhap")
74
  try:
75
- async with httpx.AsyncClient(timeout=30) as client:
76
- resp = await client.get(f"{ADMIN_API_URL}/backup/list", headers=_worker_headers(token))
77
  if resp.status_code != 200:
78
  data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {"error": resp.text}
79
  raise HTTPException(resp.status_code, data.get("error", "Worker error"))
@@ -105,10 +173,10 @@ async def backup_zone(zone_name: str, request: Request, background_tasks: Backgr
105
  try:
106
  archive_path = _create_zone_archive(zone_name)
107
  try:
108
- with httpx.Client(timeout=300) as client:
109
  with open(archive_path, "rb") as f:
110
  resp = client.post(
111
- f"{ADMIN_API_URL}/backup/upload/{zone_name}",
112
  headers={**_worker_headers(token), "Content-Type": "application/octet-stream"},
113
  content=f.read(),
114
  )
@@ -153,10 +221,10 @@ async def backup_all(request: Request, background_tasks: BackgroundTasks):
153
  _backup_status["progress"] = f"Dang backup zone {zone_name} ({done + 1}/{total})..."
154
  archive_path = _create_zone_archive(zone_name)
155
  try:
156
- with httpx.Client(timeout=300) as client:
157
  with open(archive_path, "rb") as f:
158
  resp = client.post(
159
- f"{ADMIN_API_URL}/backup/upload/{zone_name}",
160
  headers={**_worker_headers(token), "Content-Type": "application/octet-stream"},
161
  content=f.read(),
162
  )
@@ -196,8 +264,8 @@ async def restore_zone(zone_name: str, request: Request, background_tasks: Backg
196
  _backup_status["error"] = None
197
  _backup_status["progress"] = f"Dang restore zone: {zone_name}..."
198
  try:
199
- with httpx.Client(timeout=300) as client:
200
- resp = client.get(f"{ADMIN_API_URL}/backup/download/{zone_name}", headers=_worker_headers(token))
201
  if resp.status_code == 404:
202
  raise ValueError(f"Backup zone '{zone_name}' khong ton tai")
203
  if resp.status_code != 200:
@@ -251,8 +319,8 @@ async def restore_all(request: Request, background_tasks: BackgroundTasks):
251
  _backup_status["error"] = None
252
  _backup_status["progress"] = "Dang restore tat ca zones..."
253
  try:
254
- with httpx.Client(timeout=30) as client:
255
- resp = client.get(f"{ADMIN_API_URL}/backup/list", headers=_worker_headers(token))
256
  if resp.status_code != 200:
257
  raise ValueError(f"Khong the lay danh sach backup: {resp.text}")
258
  backup_list = resp.json()
@@ -262,8 +330,8 @@ async def restore_all(request: Request, background_tasks: BackgroundTasks):
262
  for b in backup_list:
263
  zone_name = b["zone_name"]
264
  _backup_status["progress"] = f"Dang restore zone {zone_name} ({done + 1}/{total})..."
265
- with httpx.Client(timeout=300) as client:
266
- resp = client.get(f"{ADMIN_API_URL}/backup/download/{zone_name}", headers=_worker_headers(token))
267
  if resp.status_code != 200:
268
  continue
269
  archive_path = BACKUP_DIR / f"{zone_name}.tar.gz"
@@ -298,4 +366,4 @@ async def restore_all(request: Request, background_tasks: BackgroundTasks):
298
  _backup_status["running"] = False
299
 
300
  background_tasks.add_task(_run)
301
- return {"ok": True, "message": "Dang restore tat ca zones trong nen..."}
 
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
 
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 _resolve_worker_ip() -> str | None:
32
+ """Resolve the Worker hostname to an IP address using system + fallback DNS."""
33
+ global _worker_ip
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, using resolved IP if needed."""
78
+ full = f"{ADMIN_API_URL}{path}"
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:
101
  """Extract JWT token from request Authorization header."""
 
105
  return ""
106
 
107
 
 
 
 
 
 
108
  def _create_zone_archive(zone_name: str) -> Path:
109
  """Create a tar.gz archive of a zone directory."""
110
  zone_path = DATA_DIR / zone_name
 
140
  if not token:
141
  raise HTTPException(401, "Chua dang nhap")
142
  try:
143
+ async with _make_client(timeout=30) as client:
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}
147
  raise HTTPException(resp.status_code, data.get("error", "Worker error"))
 
173
  try:
174
  archive_path = _create_zone_archive(zone_name)
175
  try:
176
+ with _make_sync_client(timeout=300) as client:
177
  with open(archive_path, "rb") as f:
178
  resp = client.post(
179
+ _url(f"/backup/upload/{zone_name}"),
180
  headers={**_worker_headers(token), "Content-Type": "application/octet-stream"},
181
  content=f.read(),
182
  )
 
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 _make_sync_client(timeout=300) as client:
225
  with open(archive_path, "rb") as f:
226
  resp = client.post(
227
+ _url(f"/backup/upload/{zone_name}"),
228
  headers={**_worker_headers(token), "Content-Type": "application/octet-stream"},
229
  content=f.read(),
230
  )
 
264
  _backup_status["error"] = None
265
  _backup_status["progress"] = f"Dang restore zone: {zone_name}..."
266
  try:
267
+ with _make_sync_client(timeout=300) as client:
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")
271
  if resp.status_code != 200:
 
319
  _backup_status["error"] = None
320
  _backup_status["progress"] = "Dang restore tat ca zones..."
321
  try:
322
+ with _make_sync_client(timeout=30) as client:
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}")
326
  backup_list = resp.json()
 
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 _make_sync_client(timeout=300) as client:
334
+ resp = client.get(_url(f"/backup/download/{zone_name}"), headers=_worker_headers(token))
335
  if resp.status_code != 200:
336
  continue
337
  archive_path = BACKUP_DIR / f"{zone_name}.tar.gz"
 
366
  _backup_status["running"] = False
367
 
368
  background_tasks.add_task(_run)
369
+ return {"ok": True, "message": "Dang restore tat ca zones trong nen..."}