Compare commits

1 Commits
new ... main

Author SHA1 Message Date
John Lancaster
fc98619d3c old WIP on the create_restic_repo script 2024-05-19 19:58:52 -05:00
10 changed files with 44 additions and 241 deletions

11
.autorestic.yaml Normal file
View File

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

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@ data/
.python-version .python-version
__pycache__ __pycache__
*.egg-info

View File

@@ -1 +0,0 @@
from .main import main

View File

@@ -1,106 +0,0 @@
#!/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

@@ -1,68 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
from typing import Iterable, Set
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()
def stop_containers(names: Iterable[str]) -> Set[Container]:
names = set(names)
stopped_containers = set()
for con in client.containers.list():
if con.name in names:
stopped_containers.add(con)
try:
con.stop()
except:
print('Error')
break
else:
print(f'Stopped [{con.short_id}] {con.name}')
return stopped_containers
def start_containers(containers: Iterable[Container]):
for con in containers:
try:
con.start()
except:
print(f'Error starting: [{con.short_id}] {con.name}')
else:
print(f'Started: [{con.short_id}] {con.name}')
@click.command()
@click.argument('src', type=click.Path(exists=True, file_okay=False, readable=True, resolve_path=True, path_type=Path))
@click.option('-ds', '--docker-stop', type=click.STRING,
help='Comma-delimited list of the container names to stop before running the backup')
def main(src: Path, docker_stop: str = None):
print(type(src).__name__, src)
if docker_stop is not None:
cons = stop_containers(docker_stop.split(','))
else:
cons = set()
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')
start_containers(cons)
if __name__ == '__main__':
main()

View File

@@ -1,16 +1,20 @@
#!/bin/bash #!/bin/bash
set -e
if [ -z "${RESTIC_DIR}" ]; then
if [ "$#" -ne 1 ]; then if [ "$#" -ne 1 ]; then
echo "$(tput setaf 1)No repo name specified$(tput sgr0)" echo "$(tput setaf 1)No repo name specified$(tput sgr0)"
echo "Usage: $0 <repo name>" echo "Usage: $0 <repo name>"
echo "Or have the RESTIC_DIR variable set"
exit 1 exit 1
else else
# Assign the directory to a variable RESTIC_DIR=$(readlink -f $1)
REPO_PATH=/mnt/backups/$1 fi
echo -e "Creating $(tput setaf 4)$REPO_PATH$(tput sgr0)"
fi fi
echo "RESTIC_DIR is $(tput setaf 4)$RESTIC_DIR$(tput sgr0)"
docker run -it --rm \ docker run -it --rm \
-v /mnt/backups:/mnt/backups \ -v $RESTIC_DIR:$RESTIC_DIR \
restic/restic \ restic/restic \
init --repo $REPO_PATH init --repo $RESTIC_DIR

View File

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

View File

@@ -1,13 +0,0 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "file-backups"
version = "0.1.0"
description = "With restic and Docker"
license = {file="LICENSE"}
requires-python = ">=3.10"
[project.scripts]
backup = "backup:main"

View File

@@ -1,6 +0,0 @@
docker
resticpy
click
rich
ipython

18
run.sh
View File

@@ -1,18 +0,0 @@
#!/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