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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ AGENTS.md
.env.production
.env.local
.env.staging

__pycache__
backups/
22 changes: 22 additions & 0 deletions api/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import requests

from config import (
API_URL,
API_KEY
)

def send_backup(result):
headers = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/json"
}

response = requests.post(
f"{API_URL}/backups",
headers=headers,
json=result.__dict__
)

response.raise_for_status()

return response.json()
4 changes: 4 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from config import TEST_BACKUP_PATH
from backup.backup import run_backup

run_backup(TEST_BACKUP_PATH)
33 changes: 33 additions & 0 deletions backup/backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from datetime import datetime
from pathlib import Path

from backup.zip import create_zip
from models.backup_result import BackupResult
from utils.checksum import sha256
from api.report import send_backup
from config import BACKUP_OUTPUT

def run_backup(source_path: str):
started = datetime.now()
filename = f"backup-{started.strftime('%Y%m%d-%H%M%S')}"

destination = str(Path(BACKUP_OUTPUT) / filename)
zip_file = create_zip(source_path, destination)
completed = datetime.now()

result = BackupResult(
file_name=Path(zip_file).name,
file_path=zip_file,
file_size=Path(zip_file).stat().st_size,
checksum=sha256(zip_file),
started_at=started.isoformat(),
completed_at=completed.isoformat(),
duration=int((completed - started).total_seconds()),
status="success",
type="files",
message=None
)

response = send_backup(result)

print(response)
8 changes: 8 additions & 0 deletions backup/zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import shutil
from pathlib import Path

def create_zip(source_path: str, destination: str) -> str:
dest_path = Path(destination)
dest_path.parent.mkdir(parents=True, exist_ok=True)
archive_path = shutil.make_archive(destination, "zip", source_path)
return archive_path
11 changes: 11 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from dotenv import load_dotenv

import os

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")
TEST_BACKUP_PATH = os.getenv("TEST_BACKUP_PATH")
15 changes: 15 additions & 0 deletions models/backup_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dataclasses import dataclass

@dataclass
class BackupResult:
file_name: str
file_path: str
file_size: int
checksum: str
started_at: str
completed_at: str
duration: int
status: str
type: str
message: str | None

7 changes: 7 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
certifi==2026.7.22
charset-normalizer==3.4.9
idna==3.18
psutil==7.2.2
python-dotenv==1.2.2
requests==2.34.2
urllib3==2.7.0
8 changes: 8 additions & 0 deletions utils/checksum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import hashlib

def sha256(filepath):
hash = hashlib.sha256()
with open(filepath, "rb") as file:
while chunk := file.read(8192):
hash.update(chunk)
return hash.hexdigest()
5 changes: 5 additions & 0 deletions windows/process-info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import psutil

for process in psutil.process_iter(["pid", "name"]):
print(process.info)