From b368cede931c4f3256552cd393caead84219412f Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:10:02 -0600 Subject: [PATCH] WIP --- .autorestic.yaml | 11 ---- .gitignore | 3 +- backup/docker_outside.py | 106 +++++++++++++++++++++++++++++++++++++++ backup/main.py | 17 ++++--- docker-compose.yml | 33 ++++++------ run.sh | 18 +++++++ 6 files changed, 153 insertions(+), 35 deletions(-) delete mode 100644 .autorestic.yaml create mode 100755 backup/docker_outside.py create mode 100755 run.sh diff --git a/.autorestic.yaml b/.autorestic.yaml deleted file mode 100644 index 7a8d19c..0000000 --- a/.autorestic.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 - -locations: - joplin: - from: /source/joplin/data - to: joplin-nfs - -backends: - joplin-nfs: - type: local - path: /backups/joplin diff --git a/.gitignore b/.gitignore index ac883ea..dcdde8c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ data/ .env .python-version -__pycache__ \ No newline at end of file +__pycache__ +*.egg-info \ No newline at end of file diff --git a/backup/docker_outside.py b/backup/docker_outside.py new file mode 100755 index 0000000..edef4d3 --- /dev/null +++ b/backup/docker_outside.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +import json +import os +from pathlib import Path +from typing import Iterable + +import click +import docker +from docker.client import DockerClient +from docker.errors import ContainerError +from docker.models.containers import Container +from rich.console import Console +from rich.highlighter import NullHighlighter + +console = Console(highlighter=NullHighlighter()) +print = console.print + +client = docker.from_env() + + +def get_project_containers(client: DockerClient, project_name: str): + return client.containers.list( + all=True, + filters={'label': [f"com.docker.compose.project={project_name}"]} + ) + + +def compose_start(client: DockerClient, project_name: str): + print(f' DOCKER COMPOSE START {project_name} '.center(50, '=')) + for con in get_project_containers(client, project_name): + try: + print(f'Starting {con.name}...'.ljust(30), end='') + con.start() + except Exception as e: + print('Failed') + console.print_exception(e) + else: + print('[green]Done[/]') + + +def compose_stop(client: DockerClient, project_name: str): + print(f' DOCKER COMPOSE STOP {project_name} '.center(50, '=')) + for con in get_project_containers(client, project_name): + try: + print(f'Stopping {con.name}...'.ljust(30), end='') + con.stop() + except Exception as e: + print('Failed') + console.print_exception(e) + else: + print('[green]Done[/]') + + +@click.command() +@click.argument('data_dir', type=click.Path(exists=True, file_okay=False, resolve_path=True, path_type=Path)) +@click.argument('repo', + type=click.Path(exists=True, file_okay=False, resolve_path=True, path_type=Path), + envvar='RESTIC_REPOSITORY') +@click.option('-p', '--project') +def run_backup_container(data_dir: Path, repo: Path, project: str): + compose_stop(client, project_name=project) + env = { + 'RESTIC_REPOSITORY': '/repo', + 'RESTIC_PASSWORD': os.environ['RESTIC_PASSWORD'] + } + volumes = { + "/var/run/docker.sock": { + "bind": "/var/run/docker.sock", + "mode": "rw" + }, + data_dir.as_posix(): { + "bind": '/data', + "mode": "rw" + }, + repo.as_posix(): { + "bind": '/repo', + "mode": "rw" + }, + '/etc/localtime': { + 'bind': '/etc/localtime', + 'mode': 'ro' + }, + '/etc/timezone': { + 'bind': '/etc/timezone', + 'mode': 'ro' + } + } + # print(json.dumps(volumes, indent=4)) + print(f'Backing up\n{data_dir}\n to\n{repo}') + + try: + resp = client.containers.run( + image='restic/restic:latest', remove=True, + hostname=os.environ['HOSTNAME'], + environment=env, volumes=volumes, + command='backup /data --dry-run' + ).decode() + print(resp) + except ContainerError as e: + print(e.stderr.decode()) + finally: + compose_start(client, project_name=project) + +if __name__ == '__main__': + run_backup_container() diff --git a/backup/main.py b/backup/main.py index b35ae63..9dd219a 100755 --- a/backup/main.py +++ b/backup/main.py @@ -7,6 +7,10 @@ import click import docker import restic from docker.models.containers import Container +from rich import print +from rich.console import Console + +console = Console() client = docker.from_env() @@ -48,13 +52,12 @@ def main(src: Path, docker_stop: str = None): else: cons = set() - pw_file = Path('/run/secrets/restic-pw').resolve() - restic.password_file = pw_file.as_posix() - - restic.backup( - paths=[''], - dry_run=True - ) + with console.status(f'Running backup'): + backup_result = restic.backup( + paths=[src], + dry_run=True + ) + print(backup_result) print(f'{len(restic.snapshots())} snapshots found in the repo') diff --git a/docker-compose.yml b/docker-compose.yml index 242e389..b84e709 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,27 @@ version: '3.9' services: - autorestic: - image: cupcakearmy/autorestic:latest - env_file: - - .env + restic: + image: restic/restic:latest + hostname: gitea + environment: + - RESTIC_REPOSITORY=/repo + - RESTIC_PASSWORD=${RESTIC_PASSWORD} volumes: - - .autorestic.yaml:/.autorestic.yaml - - ../:/source:ro - - backups:/backups - - /var/run/docker.sock:/var/run/docker.sock - command: [ - "autorestic", - "backup", - "-va", - "-c", "/.autorestic.yaml" - ] + - ../gitea/data:/data:ro + - restic_repo:/repo + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + command: backup /data --dry-run volumes: - backups: + restic_repo: driver: local driver_opts: type: none o: bind - device: /mnt/backups \ No newline at end of file + device: ${RESTIC_REPOSITORY} + +secrets: + restic-pw: + environment: RESTIC_PASSWORD diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..6a04113 --- /dev/null +++ b/run.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +SCRIPT_DIR=$(readlink -f $(dirname "${BASH_SOURCE[0]}")) +COMPOSE_FILE=$SCRIPT_DIR/docker-compose.yml + +echo "Retic repo: $RESTIC_REPOSITORY" + +# docker compose -f $COMPOSE_FILE \ +# run -it --rm backup backup --json "$@" | \ +# sed 's/^[^{]*//' | \ +# jq 'select(.message_type != "status")' + +PROJECT_NAME=$1 +shift + +docker compose --project-name $PROJECT_NAME stop +docker compose -f $COMPOSE_FILE run -it --rm restic backup --verbose "$@" +docker compose --project-name $PROJECT_NAME start