This commit is contained in:
John Lancaster
2024-02-19 23:10:02 -06:00
parent 3c89fe22bc
commit b368cede93
6 changed files with 153 additions and 35 deletions

View File

@@ -1,11 +0,0 @@
version: 2
locations:
joplin:
from: /source/joplin/data
to: joplin-nfs
backends:
joplin-nfs:
type: local
path: /backups/joplin

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ data/
.env .env
.python-version .python-version
__pycache__ __pycache__
*.egg-info

106
backup/docker_outside.py Executable file
View File

@@ -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()

View File

@@ -7,6 +7,10 @@ import click
import docker import docker
import restic import restic
from docker.models.containers import Container from docker.models.containers import Container
from rich import print
from rich.console import Console
console = Console()
client = docker.from_env() client = docker.from_env()
@@ -48,13 +52,12 @@ def main(src: Path, docker_stop: str = None):
else: else:
cons = set() cons = set()
pw_file = Path('/run/secrets/restic-pw').resolve() with console.status(f'Running backup'):
restic.password_file = pw_file.as_posix() backup_result = restic.backup(
paths=[src],
restic.backup( dry_run=True
paths=[''], )
dry_run=True print(backup_result)
)
print(f'{len(restic.snapshots())} snapshots found in the repo') print(f'{len(restic.snapshots())} snapshots found in the repo')

View File

@@ -1,26 +1,27 @@
version: '3.9' version: '3.9'
services: services:
autorestic: restic:
image: cupcakearmy/autorestic:latest image: restic/restic:latest
env_file: hostname: gitea
- .env environment:
- RESTIC_REPOSITORY=/repo
- RESTIC_PASSWORD=${RESTIC_PASSWORD}
volumes: volumes:
- .autorestic.yaml:/.autorestic.yaml - ../gitea/data:/data:ro
- ../:/source:ro - restic_repo:/repo
- backups:/backups - /etc/timezone:/etc/timezone:ro
- /var/run/docker.sock:/var/run/docker.sock - /etc/localtime:/etc/localtime:ro
command: [ command: backup /data --dry-run
"autorestic",
"backup",
"-va",
"-c", "/.autorestic.yaml"
]
volumes: volumes:
backups: restic_repo:
driver: local driver: local
driver_opts: driver_opts:
type: none type: none
o: bind o: bind
device: /mnt/backups device: ${RESTIC_REPOSITORY}
secrets:
restic-pw:
environment: RESTIC_PASSWORD

18
run.sh Executable file
View File

@@ -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