diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a66c47a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.egg-info \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7d67656 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "restic" +version = "0.1.0" +description = "Lightweight wrapper around restic" +authors = [ + { name = "John Lancaster", email = "32917998+jsl12@users.noreply.github.com" }, +] +license = { file = "LICENSE" } +requires-python = ">=3.10" + +dependencies = ["rich", "requests"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..749b7c7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +uv +ruff +rich +requests \ No newline at end of file diff --git a/src/restic/__init__.py b/src/restic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/restic/backup.py b/src/restic/backup.py new file mode 100755 index 0000000..fec943a --- /dev/null +++ b/src/restic/backup.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import json +import logging +import subprocess + +from rich.console import Console +from rich.progress import Progress + +from .console import console, logger +from .loki import send_to_loki + + +def main(command: str, loki_url: str): + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True + ) + + with process.stdout, Progress(console=console, transient=True) as progress: + task = progress.add_task('Running backup', total=1.0) + for line in iter(process.stdout.readline, ''): + data = json.loads(line) + if data['message_type'] == 'status': + progress.update(task, completed=data['percent_done']) + progress.update(task, completed=1.0, refresh=True) + + logger.info( + f'[yellow]{data["snapshot_id"]}[/] done, with {data["files_new"]} new files, {data["files_changed"]} files changed. Added {data["data_added"] / 10**6:.1f} MB out of {data["total_bytes_processed"] / 10**6:.1f} MB' + ) + + send_to_loki(loki_url=loki_url, line=line, backup='summary') + + +if __name__ == '__main__': + import os + + from rich.console import Console + from rich.logging import RichHandler + + console = Console() + + logging.basicConfig( + level='DEBUG', format='%(message)s', handlers=[RichHandler(markup=True, console=console)] + ) + + cmd = f'restic backup {os.environ["BACKUP_DIR"]} --tag python-script --json' + main(cmd, 'http://192.168.1.107:3100/loki/api/v1/push') diff --git a/src/restic/console.py b/src/restic/console.py new file mode 100644 index 0000000..dd0c2da --- /dev/null +++ b/src/restic/console.py @@ -0,0 +1,7 @@ +import logging + +from rich.console import Console + +console = Console() + +logger = logging.getLogger('restic_parser') diff --git a/src/restic/loki.py b/src/restic/loki.py new file mode 100644 index 0000000..9bcbb91 --- /dev/null +++ b/src/restic/loki.py @@ -0,0 +1,25 @@ +import os +from time import time + +import requests + +from .console import logger + + +def send_to_loki(loki_url: str, line: str, backup: str): + ns = round(time() * 1_000_000_000) + + payload = { + 'streams': [ + { + 'stream': {'host': os.environ['HOSTNAME'], 'backup': backup}, + 'values': [[str(ns), line]], + } + ] + } + try: + resp = requests.post(loki_url, json=payload, timeout=1) + except Exception as e: + logger.exception(e) + else: + logger.info(f'Sent line to loki at {loki_url} {resp.text}')