helpers file optimizations

This commit is contained in:
Esa Kataja
2025-05-14 11:41:55 +03:00
parent 21aa75e170
commit f719ad5d80
+121 -27
View File
@@ -5,6 +5,11 @@ from datetime import datetime
from subprocess import run from subprocess import run
import os import os
import typer import typer
from typing import Optional
# Constants
JSON_TARGETS = Path(__file__).parent / "targets.json"
EXCLUDES_PATH = Path(__file__).parent / "excludes.txt"
# Default exclude patterns for backups # Default exclude patterns for backups
default_excludes = [ default_excludes = [
@@ -48,18 +53,41 @@ default_excludes = [
def ask_repokey() -> None: def ask_repokey() -> None:
"""
Prompt user for Borg repository key and set it as environment variable.
"""
repokey = typer.prompt("Enter repository key: ", hide_input=True) repokey = typer.prompt("Enter repository key: ", hide_input=True)
os.environ["BORG_PASSPHRASE"] = repokey os.environ["BORG_PASSPHRASE"] = repokey
def get_targets() -> list[BackupItem]: def get_targets() -> list[BackupItem]:
JSON_TARGETS = Path(__file__).parent / "targets.json" """
Load and parse backup targets from the JSON configuration file.
Returns:
List of BackupItem objects representing the targets.
Empty list if the file doesn't exist or contains invalid data.
"""
try:
with open(JSON_TARGETS, "r") as f: with open(JSON_TARGETS, "r") as f:
targets = json.load(f) targets = json.load(f)
return [BackupItem(**target) for target in targets] return [BackupItem(**target) for target in targets]
except (json.JSONDecodeError, FileNotFoundError) as e:
typer.echo(f"Error loading targets: {e}")
return []
def add_target(target: BackupItem) -> None: def add_target(target: BackupItem) -> bool:
"""
Add a new backup target to the configuration.
Args:
target: BackupItem object to add
Returns:
bool: True if successful, False otherwise
"""
try:
# Get current targets # Get current targets
targets = get_targets() targets = get_targets()
@@ -68,11 +96,23 @@ def add_target(target: BackupItem) -> None:
# Save targets # Save targets
write_targets(targets) write_targets(targets)
return True
except Exception as e:
typer.echo(f"Error adding target: {e}")
return False
def write_targets(targets: list[BackupItem]) -> None: def write_targets(targets: list[BackupItem]) -> bool:
"""Save targets to JSON file""" """
JSON_TARGETS = Path(__file__).parent / "targets.json" Save targets to JSON file.
Args:
targets: List of BackupItem objects to save
Returns:
bool: True if successful, False otherwise
"""
try:
with open(JSON_TARGETS, "w") as f: with open(JSON_TARGETS, "w") as f:
json.dump( json.dump(
[target.json_serializable for target in targets], [target.json_serializable for target in targets],
@@ -80,46 +120,93 @@ def write_targets(targets: list[BackupItem]) -> None:
ensure_ascii=False, ensure_ascii=False,
indent=2, indent=2,
) )
return True
except Exception as e:
typer.echo(f"Error writing targets: {e}")
return False
def set_targets(targets: list[BackupItem]) -> None: # The set_targets function has been removed as it was redundant with write_targets
JSON_TARGETS = Path(__file__).parent / "targets.json"
with open(JSON_TARGETS, "w") as f:
json.dump(
[target.json_serializable for target in targets],
f,
ensure_ascii=False,
indent=2,
)
def prune_backups(targets: list[BackupItem]) -> None: def prune_backups(targets: list[BackupItem], daily: int = 1, weekly: int = 2,
monthly: int = 3, yearly: int = 1) -> list[dict]:
"""
Prune old backups according to retention policy and compact the repository.
Args:
targets: List of backup targets to prune
daily: Number of daily backups to keep
weekly: Number of weekly backups to keep
monthly: Number of monthly backups to keep
yearly: Number of yearly backups to keep
Returns:
List of dictionaries containing results for each target
"""
results = []
for target in targets: for target in targets:
if not target.enabled: if not target.enabled:
continue continue
result = {"target": target.path, "success": False, "message": ""}
try:
cmd = [ cmd = [
"borg", "borg",
"prune", "prune",
"--keep-daily", "--keep-daily", str(daily),
"1", "--keep-weekly", str(weekly),
"--keep-weekly", "--keep-monthly", str(monthly),
"2", "--keep-yearly", str(yearly),
"--keep-monthly",
"3",
"--keep-yearly",
"1",
target.target, target.target,
] ]
print("Pruning target:", target.path) print("Pruning target:", target.path)
run(cmd, capture_output=True) prune_result = run(cmd, capture_output=True, text=True)
if prune_result.returncode != 0:
result["message"] = f"Prune failed: {prune_result.stderr}"
results.append(result)
continue
print("Pruned target:", target.path) print("Pruned target:", target.path)
print("Compacting cache") print("Compacting cache")
run(["borg", "compact", target.target], capture_output=True) compact_result = run(["borg", "compact", target.target], capture_output=True, text=True)
if compact_result.returncode != 0:
result["message"] = f"Compact failed: {compact_result.stderr}"
results.append(result)
continue
print("Compacted cache") print("Compacted cache")
result["success"] = True
result["message"] = "Pruned and compacted successfully"
except Exception as e:
result["message"] = f"Error during pruning: {str(e)}"
results.append(result)
return results
def run_backup(target: BackupItem) -> None: def run_backup(target: BackupItem) -> Optional[ResultInfo]:
"""
Execute backup for the specified target using borg.
Args:
target: The backup target configuration
Returns:
ResultInfo object with backup statistics or None if backup failed
Raises:
Exception: If the backup process fails
"""
date_str = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") date_str = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
try:
# Build borg command with base options # Build borg command with base options
cmd = [ cmd = [
"borg", "borg",
@@ -127,7 +214,7 @@ def run_backup(target: BackupItem) -> None:
"-C", "-C",
f"{target.compression.value},{target.comp_ratio}", f"{target.compression.value},{target.comp_ratio}",
"--exclude-from", "--exclude-from",
"/home/esa/bin/backupper/src/excludes.txt", EXCLUDES_PATH,
"--stats", "--stats",
"--json", "--json",
f"{target.target}::{date_str}", f"{target.target}::{date_str}",
@@ -138,12 +225,19 @@ def run_backup(target: BackupItem) -> None:
if target.excludes: # This is true if the list is not empty if target.excludes: # This is true if the list is not empty
cmd.insert(3, "--exclude") cmd.insert(3, "--exclude")
cmd.insert(4, ",".join(target.excludes)) cmd.insert(4, ",".join(target.excludes))
print("Running target:", target.path) print("Running target:", target.path)
result = run(cmd, capture_output=True) result = run(cmd, capture_output=True)
if result.returncode != 0: if result.returncode != 0:
print("Result:", result.stderr.decode("utf-8")) print("Result:", result.stderr.decode("utf-8"))
raise Exception(result.stderr.decode("utf-8")) raise Exception(result.stderr.decode("utf-8"))
result_info = ResultInfo(**json.loads(result.stdout.decode("utf-8"))) result_info = ResultInfo(**json.loads(result.stdout.decode("utf-8")))
prune_backups([target]) prune_backups([target])
return result_info return result_info
except Exception as e:
print(f"Backup failed: {str(e)}")
return None