WIP
This commit is contained in:
@@ -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
3
.gitignore
vendored
@@ -2,4 +2,5 @@ data/
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
.python-version
|
.python-version
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.egg-info
|
||||||
106
backup/docker_outside.py
Executable file
106
backup/docker_outside.py
Executable 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()
|
||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
18
run.sh
Executable 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
|
||||||
Reference in New Issue
Block a user