Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc98619d3c |
11
.autorestic.yaml
Normal file
11
.autorestic.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
|
||||
locations:
|
||||
joplin:
|
||||
from: /source/joplin/data
|
||||
to: joplin-nfs
|
||||
|
||||
backends:
|
||||
joplin-nfs:
|
||||
type: local
|
||||
path: /backups/joplin
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,5 +2,4 @@ data/
|
||||
.env
|
||||
|
||||
.python-version
|
||||
__pycache__
|
||||
*.egg-info
|
||||
__pycache__
|
||||
@@ -1 +0,0 @@
|
||||
from .main import main
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -1,16 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "$(tput setaf 1)No repo name specified$(tput sgr0)"
|
||||
echo "Usage: $0 <repo name>"
|
||||
exit 1
|
||||
else
|
||||
# Assign the directory to a variable
|
||||
REPO_PATH=/mnt/backups/$1
|
||||
echo -e "Creating $(tput setaf 4)$REPO_PATH$(tput sgr0)"
|
||||
if [ -z "${RESTIC_DIR}" ]; then
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "$(tput setaf 1)No repo name specified$(tput sgr0)"
|
||||
echo "Usage: $0 <repo name>"
|
||||
echo "Or have the RESTIC_DIR variable set"
|
||||
exit 1
|
||||
else
|
||||
RESTIC_DIR=$(readlink -f $1)
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "RESTIC_DIR is $(tput setaf 4)$RESTIC_DIR$(tput sgr0)"
|
||||
|
||||
docker run -it --rm \
|
||||
-v /mnt/backups:/mnt/backups \
|
||||
-v $RESTIC_DIR:$RESTIC_DIR \
|
||||
restic/restic \
|
||||
init --repo $REPO_PATH
|
||||
init --repo $RESTIC_DIR
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
restic:
|
||||
image: restic/restic:latest
|
||||
hostname: gitea
|
||||
environment:
|
||||
- RESTIC_REPOSITORY=/repo
|
||||
- RESTIC_PASSWORD=${RESTIC_PASSWORD}
|
||||
autorestic:
|
||||
image: cupcakearmy/autorestic:latest
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ../gitea/data:/data:ro
|
||||
- restic_repo:/repo
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
command: backup /data --dry-run
|
||||
- .autorestic.yaml:/.autorestic.yaml
|
||||
- ../:/source:ro
|
||||
- backups:/backups
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: [
|
||||
"autorestic",
|
||||
"backup",
|
||||
"-va",
|
||||
"-c", "/.autorestic.yaml"
|
||||
]
|
||||
|
||||
volumes:
|
||||
restic_repo:
|
||||
backups:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: ${RESTIC_REPOSITORY}
|
||||
|
||||
secrets:
|
||||
restic-pw:
|
||||
environment: RESTIC_PASSWORD
|
||||
device: /mnt/backups
|
||||
@@ -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"
|
||||
@@ -1,6 +0,0 @@
|
||||
docker
|
||||
resticpy
|
||||
click
|
||||
rich
|
||||
|
||||
ipython
|
||||
18
run.sh
18
run.sh
@@ -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
|
||||
Reference in New Issue
Block a user