Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ API_URL=
API_KEY=

TEST_BACKUP_PATH=

R2_ACCOUNT_ID=""
R2_ACCESS_KEY_ID=""
R2_SECRET_ACCESS_KEY=""
R2_BUCKET_NAME="my-bucket"
R2_ENDPOINT_URL=""
R2_ENABLED="true"
17 changes: 15 additions & 2 deletions backup/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from models.backup_result import BackupResult
from utils.checksum import sha256
from api.report import send_backup, store_job_report
from storage.r2 import upload_file
from config import BACKUP_OUTPUT

def run_backup_job(job: dict):
Expand All @@ -29,6 +30,12 @@ def run_backup_job(job: dict):
completed = datetime.now()
duration = int((completed - started).total_seconds())

r2_status = upload_file(zip_file, Path(zip_file).name)
if r2_status["success"]:
message = f"Backup completed successfully and uploaded to R2 bucket '{r2_status.get('bucket')}'"
else:
message = f"Backup completed locally. (R2: {r2_status['message']})"

result = BackupResult(
file_name=Path(zip_file).name,
file_path=zip_file,
Expand All @@ -40,7 +47,7 @@ def run_backup_job(job: dict):
duration=duration,
status="success",
type="files",
message="Backup completed successfully"
message=message
)
except Exception as e:
completed = datetime.now()
Expand Down Expand Up @@ -82,6 +89,12 @@ def run_backup(source_path: str | dict):
completed = datetime.now()
duration = int((completed - started).total_seconds())

r2_status = upload_file(zip_file, Path(zip_file).name)
if r2_status["success"]:
message = f"Backup completed successfully and uploaded to R2 bucket '{r2_status.get('bucket')}'"
else:
message = f"Backup completed locally. (R2: {r2_status['message']})"

result = BackupResult(
file_name=Path(zip_file).name,
file_path=zip_file,
Expand All @@ -93,7 +106,7 @@ def run_backup(source_path: str | dict):
duration=duration,
status="success",
type="files",
message="Backup completed successfully"
message=message
)
except Exception as e:
completed = datetime.now()
Expand Down
14 changes: 11 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
load_dotenv()

APP_NAME = os.getenv("APP_NAME")
API_URL = os.getenv("API_URL")
API_KEY = os.getenv("API_KEY")
BACKUP_OUTPUT = os.getenv("BACKUP_OUTPUT", "backups")
API_URL = os.getenv("API_URL")
API_KEY = os.getenv("API_KEY")

BACKUP_OUTPUT = os.getenv("BACKUP_OUTPUT", "backups")
TEST_BACKUP_PATH = os.getenv("TEST_BACKUP_PATH")

R2_ACCOUNT_ID = os.getenv("R2_ACCOUNT_ID")
R2_ACCESS_KEY_ID = os.getenv("R2_ACCESS_KEY_ID")
R2_SECRET_ACCESS_KEY = os.getenv("R2_SECRET_ACCESS_KEY")
R2_BUCKET_NAME = os.getenv("R2_BUCKET_NAME")
R2_ENDPOINT_URL = os.getenv("R2_ENDPOINT_URL")
R2_ENABLED = os.getenv("R2_ENABLED", "false").lower() in ("true", "1", "yes")
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
boto3==1.34.145
certifi==2026.7.22
charset-normalizer==3.4.9
idna==3.18
Expand Down
74 changes: 74 additions & 0 deletions storage/r2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os
from pathlib import Path
import boto3
from botocore.config import Config

from config import (
R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY,
R2_BUCKET_NAME,
R2_ENDPOINT_URL,
R2_ENABLED
)

def get_r2_client():
if not R2_ENABLED:
return None

if not R2_ACCESS_KEY_ID or not R2_SECRET_ACCESS_KEY or not R2_ENDPOINT_URL:
print("[R2] Warning: R2 credentials or endpoint URL missing.")
return None

return boto3.client(
"s3",
endpoint_url=R2_ENDPOINT_URL,
aws_access_key_id=R2_ACCESS_KEY_ID,
aws_secret_access_key=R2_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4")
)

def upload_file(file_path: str, object_key: str | None = None) -> dict:
if not R2_ENABLED:
return {
"success": False,
"message": "R2 upload is disabled"
}

path = Path(file_path)
if not path.exists():
return {
"success": False,
"message": f"Local file not found: {file_path}"
}

key = object_key or path.name

try:
client = get_r2_client()
if not client:
return {
"success": False,
"message": "R2 client initialization failed"
}

print(f"[R2] Uploading {path.name} to bucket '{R2_BUCKET_NAME}' as '{key}'...")
client.upload_file(
str(path),
R2_BUCKET_NAME,
key
)
print(f"[R2] Upload successfully completed: {key}")

return {
"success": True,
"bucket": R2_BUCKET_NAME,
"object_key": key,
"message": "Uploaded to R2 successfully"
}
except Exception as e:
err_msg = f"R2 upload error: {str(e)}"
print(f"[R2] {err_msg}")
return {
"success": False,
"message": err_msg
}