From 560f291db97ceb19bf9e5f6190ef737a26954f42 Mon Sep 17 00:00:00 2001 From: Florent Guiotte Date: Sun, 11 Aug 2024 21:59:44 +0200 Subject: [PATCH] Add backup script --- README.md | 8 ++++ backup.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100755 backup.py diff --git a/README.md b/README.md index 7c13b86..467d5ea 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ dcc restart nextcloud-db dcc up -d nextcloud ``` +## Backup + +To backup the named volumes, run: + +```shell +./backup.py +``` + ## Logs 2022-11-02 Created OVH token diff --git a/backup.py b/backup.py new file mode 100755 index 0000000..c0b06f6 --- /dev/null +++ b/backup.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# file backup.py +# author Florent Guiotte +# version 0.0 +# date 10 août 2024 +"""Abstract + +doc. +""" + +import yaml +from pathlib import Path +import subprocess +from datetime import datetime + + +COMPOSE_PATH = Path('docker-compose.yml') +BACKUP_PATH = Path('/mnt/storage/docker-bkp') +VOLUME_PREFIX = 'docker_' + + +class UnionFind: + def __init__(self): + self.parent = {} + + def make(self, service): + self.parent.setdefault(service, service) + + def find(self, service): + """return root""" + if self.parent[service] != service: + self.parent[service] = self.find(self.parent[service]) + + return self.parent[service] + + def union(self, service1, service2): + root1 = self.find(service1) + root2 = self.find(service2) + if root1 != root2: + self.parent[root2] = root1 # compress! + +def build_services_graph(services): + uf = UnionFind() + + for service in services: + uf.make(service) + for dependency in services[service].get('depends_on', []): + uf.make(dependency) + uf.union(service, dependency) + + return uf + +def group_services(services, graph): + grouped_services = {} + for service in services: + root = graph.find(service) + if root not in grouped_services: + grouped_services[root] = {'services': []} + grouped_services[root]['services'].append(service) + + return grouped_services + + +def group_volumes(services, volumes, services_group): + for group_name, group in services_group.items(): + group_volumes = group.setdefault('volumes', []) + for service in group['services']: + for volume in [v.split(':')[0] for v in services[service]['volumes']]: + if volume in volumes: group_volumes += [volume] + + return services_group + +def backup(volume): + current_date = datetime.now() + date_string = current_date.strftime("%Y-%m-%d") + archive_name = f'{date_string}_{volume}.tar' + print(f'backup volume {volume} to {BACKUP_PATH}/{archive_name}') + + subprocess.run(f'docker run --rm --volume {VOLUME_PREFIX}{volume}:/data --volume {BACKUP_PATH}:/bkp ubuntu tar -cf /bkp/{archive_name} -C /data .'.split()) + + +def run_docker_compose(cmd): + subprocess.run(f'docker compose {cmd}'.split()) + + +if __name__ == '__main__': + with COMPOSE_PATH.open() as cf: + compose = yaml.safe_load(cf) + + services = compose['services'] + volumes = compose['volumes'] + + + services_graph = build_services_graph(services) + services_group = group_services(services, services_graph) + services_group = group_volumes(services, volumes, services_group) + + for group_name, group in services_group.items(): + print(f'Service group {group_name} ', end='') + + if not group['volumes']: + print('no volumes') + continue + + print('run backup...') + + run_docker_compose(f'stop {" ".join(group["services"])}') + for volume in group['volumes']: + backup(volume) + run_docker_compose(f'start {" ".join(group["services"])}')