diff --git a/backupbot.py b/backupbot.py index 8409e2e..b57e536 100755 --- a/backupbot.py +++ b/backupbot.py @@ -9,9 +9,11 @@ import docker import restic from restic.errors import ResticFailedError from pathlib import Path +from shutil import copyfile, rmtree #logging.basicConfig(level=logging.INFO) VOLUME_PATH = "/var/lib/docker/volumes/" +SECRET_PATH = '/secrets/' SERVICE = None @click.group() @@ -53,14 +55,15 @@ def export_secrets(): @cli.command() def create(): pre_commands, post_commands, backup_paths, apps = get_backup_cmds() + copy_secrets(apps) + backup_paths.append(SECRET_PATH) run_commands(pre_commands) backup_volumes(backup_paths, apps) run_commands(post_commands) def get_backup_cmds(): client = docker.from_env() - containers = dict(map(lambda c: ( - c.labels['com.docker.swarm.service.name'], c), client.containers.list())) + container_by_service = {c.labels['com.docker.swarm.service.name']: c for c in client.containers.list()} backup_paths = set() backup_apps = set() pre_commands = {} @@ -73,7 +76,9 @@ def get_backup_cmds(): if SERVICE and SERVICE != stack_name: continue backup_apps.add(stack_name) - container = containers[s.name] + container = container_by_service.get(s.name) + if not container: + logging.error("Container {s.name} is not running, hooks can not be executed") if prehook:= labels.get('backupbot.backup.pre-hook'): pre_commands[container] = prehook if posthook:= labels.get('backupbot.backup.post-hook'): @@ -82,6 +87,25 @@ def get_backup_cmds(): Path(VOLUME_PATH).glob(f"{stack_name}_*")) return pre_commands, post_commands, list(backup_paths), list(backup_apps) +def copy_secrets(apps): + rmtree(SECRET_PATH, ignore_errors=True) + os.mkdir(SECRET_PATH) + client = docker.from_env() + container_by_service = {c.labels['com.docker.swarm.service.name']: c for c in client.containers.list()} + services = client.services.list() + for s in services: + app_name = s.attrs['Spec']['Labels']['com.docker.stack.namespace'] + if app_name in apps: + if app_secs:= s.attrs['Spec']['TaskTemplate']['ContainerSpec'].get('Secrets'): + if not container_by_service.get(s.name): + logging.error("Container {s.name} is not running, secrets can not be copied.") + continue + container_id = container_by_service[s.name].id + for sec in app_secs: + src = f'/var/lib/docker/containers/{container_id}/mounts/secrets/{sec["SecretID"]}' + dst = SECRET_PATH + sec['SecretName'] + copyfile(src, dst) + def run_commands(commands): for container, command in commands.items(): if not command: diff --git a/compose.yml b/compose.yml index f6c0511..13c591d 100644 --- a/compose.yml +++ b/compose.yml @@ -6,6 +6,7 @@ services: volumes: - "/var/run/docker.sock:/var/run/docker.sock" - "/var/lib/docker/volumes/:/var/lib/docker/volumes/" + - "/var/lib/docker/containers/:/var/lib/docker/containers/:ro" - backups:/backups environment: - CRON_SCHEDULE