diff --git a/configuration.ini.example b/configuration.ini.example index b58b05f..e0bdda8 100644 --- a/configuration.ini.example +++ b/configuration.ini.example @@ -7,4 +7,5 @@ sshfolder=path to the .ssh folder to use [restic] remotehost= repository= -passwordfile= \ No newline at end of file +passwordfile= +dryrun= \ No newline at end of file diff --git a/main.py b/main.py index 1af3b6f..584a136 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ passwordFile = None def arguments_parser(): ''' récupère les arguments de la commande - :return: + :return: object with arguments as members ''' parser = argparse.ArgumentParser( prog='BackandUp', @@ -38,78 +38,67 @@ def arguments_parser(): help='Patterns of volume or bind to include from the backup. Using this argument, ' 'only matching volumes and binds will be backed up.') - args = parser.parse_args() - bindincludepattern = args.bindinclude - bindexcludepattern = args.bindexclude - configfile = args.config - composefile = args.compose - sshfolder = args.sshfolder - remotehost = None - repository = None - passwordfile = None + return parser.parse_args() def read_config_file(filepath: str = "configuration.ini"): parser = configparser.ConfigParser() parser.read(filepath) - global bindingIncludePattern - global bindingExcludePattern - global remoteHost - global repository - global passwordFile - bindingIncludePattern = parser["backandup"]["bindincludepattern"] - bindingExcludePattern = parser["backandup"]["bindexcludepattern"] - remoteHost = parser["restic"]["remotehost"] - remoteUser = parser["restic"]["remoteuser"] - repository = parser["restic"]["repository"] - passwordFile = parser["restic"]["passwordfile"] + global arguments + if "backandup" in parser: + if "composefile" in parser["backandup"]: + arguments.compose = parser["backandup"]["composefile"] + if "composefile" in parser["backandup"]: + arguments.sshfolder = parser["backandup"]["sshfolder"] + if "composefile" in parser["backandup"]: + arguments.bindinclude = parser["backandup"]["bindincludepattern"] + if "composefile" in parser["backandup"]: + arguments.bindexclude = parser["backandup"]["bindexcludepattern"] -def do_backup(dirpath: str, tags: str = None): - restic.repository = f"sftp:{remoteUser}@{remoteHost}:/{repository}" - restic.password_file = passwordFile - if tags is None: - restic.backup(paths=[dirpath]) - else: - restic.backup(paths=[dirpath], tags=[]) - - -def backup_ct_binds(ct: docker.models.containers.Container, includepattern: str | list = None, - excludepattern: str | list = None): - if ct.attrs['HostConfig']['Binds'] is None: - print(" Nothing to backup") - return 0 - if type(includepattern) is str: - includepattern = [includepattern] - if type(excludepattern) is str: - excludepattern = [excludepattern] - if includepattern is None: - includepattern = ["NOPATTERNTOINCLUDEXXXXXX"] - if excludepattern is None: - excludepattern = ["NOPATTERNTOEXCLUDEXXXXXX"] - for BindMount in ct.attrs['HostConfig']['Binds']: - BindPath = BindMount.split(":")[0] - print(f" {BindPath}") - # Si on matche au moins 1 pattern d'inclusion ET aucun pattern d'exclusion - if any(x in BindPath for x in includepattern) and not any(x in BindPath for x in excludepattern): - do_backup(BindPath) + if "restic" in parser: + arguments.remotehost = parser["restic"]["remotehost"] + arguments.repository = parser["restic"]["repository"] + arguments.passwordfile = parser["restic"]["passwordfile"] + if "dryrun" in parser["restic"]: + arguments.dryrun = (parser["restic"]["dryrun"].lower() == "true" or parser["restic"]["dryrun"] > 0) else: - print(" not a directory to backup") - return 0 + arguments.dryrun = False -def stop_backup_restart_container(ct: docker.models.containers.Container): - print(ct.name) - dorestart = False - if ct.attrs['State']['Status'] == 'running': - print(" stop the container") - dorestart = True - ct.stop() - backup_ct_binds(ct, bindingIncludePattern, bindingExcludePattern) - if dorestart: - print(" start the container") - ct.start() - return 0 +def restic_backup(dirpath: str, tagslist: list = None): + ''' + Lance la sauvegarde avec un container restic one-shot + :param dirpath: str + :param tags: list + :return: + ''' + dockerhost = docker.from_env() + ctpath = dirpath + # si il s'agit d'un volume on ne peut pas deviner son point de montage alors on le met arbitrairement à la racine + if not os.path.exists(dirpath): + ctpath = f"/{ctpath}" + + tagscommand = "" + if tagslist is not None and len(tagslist): + for tag in tagslist: + tagscommand = f"{tagscommand}--tag {tag} " + tagscommand = tagscommand[:-1] + + dryrun = "" + if arguments.dryrun: + dryrun = "--dry-run" + + volumes = {dirpath: {'bind': dirpath, 'mode': 'ro'}, + arguments.sshfolder: {'bind': '/root/.ssh', 'mode': 'ro'}, + arguments.passwordfile: {'bind': "/repopassword", 'mode': 'ro'}} + command = f"backup {dryrun} {tagscommand} {dirpath}" + hostname = "restic" + environment = ["RESTIC_PASSWORD_FILE=/repopassword", + f"RESTIC_REPOSITORY={arguments.repository}"] + + dockerhost.containers.run("restic/restic", command=command, environment=environment, + volumes=volumes, remove=True, hostname=hostname) def yaml_to_services_list(filepath: str) -> dict|None: @@ -188,9 +177,10 @@ def backup_services(servicename: str, servicesdict: dict, composefilepath: str) :param composefilepath: str :return: bool ''' + print(f"Starting process for {servicename}:") if servicesdict[servicename]["backupdone"]: - print(f"backup is already done for {servicename}, ignoring") + print(f" backup is already done, ignoring") return True dockerclient = docker.from_env() @@ -199,67 +189,39 @@ def backup_services(servicename: str, servicesdict: dict, composefilepath: str) for containerid in containersid: container = dockerclient.containers.get(containerid) containerrunning.append(container.status == "running") - print(f"stopping {container.name}") + print(f" stopping {container.name}") container.stop() if len(servicesdict[servicename]["must_before"]): for dependencie in servicesdict[servicename]["must_before"]: - backup_service(dependencie, servicesdict, composefilepath) + backup_services(dependencie, servicesdict, composefilepath) # TODO: faire la sauvegarde - print(f"doing backup of {servicename}") + for containerid in containersid: + container = dockerclient.containers.get(containerid) + if "Binds" in container.attrs["HostConfig"] and len(container.attrs["HostConfig"]["Binds"]): + for bind in container.attrs["HostConfig"]["Binds"]: + bindpath = bind.split(":")[0] + print(f" backup of {bindpath}") + restic_backup(dirpath=bindpath, tagslist=[servicename]) for containerindex in range(0, len(containersid)): if containerrunning[containerindex]: container = dockerclient.containers.get(containersid[containerindex]) - print(f"restarting {container.name}") + print(f" restarting {container.name}") container.start() servicesdict[servicename]["backupdone"] = True return True -def volume_backup(itemtobackup: str) -> bool: - ''' - Exécute la sauvegarde du dossier/fichier bind ou volume du container. - :param itemtobackup: str - :return: bool - ''' - # il faut lancer l'équivalent de ceci : - # docker run --rm -v $(itemtobackup):/$(itemtobackup) -v $passwordfile:/$passwordfile -v $sshfolder:/root/.ssh \ - # --host $host restic/restic restic --password-file /$passwordfile -r $remotehost backup /$(itemtobackup) - - volumes = {sshfolder: {'bind': '/root/.ssh', 'mode': 'ro'}, - passwordfile: {'bind': '/.resticpass', 'mode': 'ro'}} - ctpath = itemtobackup - if itemtobackup.find("/") == -1 : - ctpath = f"/{itemtobackup}" - - dockerclient = docker.from_env() - backuplog = dockerclient.containers.run("restic/restic", command="Lol") - - - - - # Press the green button in the gutter to run the script. if __name__ == '__main__': - dockerhost = docker.from_env() + dockerhost = docker.from_env() + arguments = arguments_parser() - bindincludepattern = None - bindexcludepattern = None - configfile = None - composefile = "/root/compose/core/docker-compose.yml" - sshfolder = "/root/.ssh" - remotehost = None - repository = None - passwordfile = None - - arguments_parser() - - # servicesDict = yaml_to_services_list(composefile) - # servicesList = order_services_by_dependencies(servicesDict) - # print("Backup process is starting") - # for servicetobackup in reversed(servicesList): - # print(f" - {servicetobackup}") - # backup_services(servicetobackup, servicesDict, composefile) + servicesDict = yaml_to_services_list(arguments.compose) + servicesList = order_services_by_dependencies(servicesDict) + print("Backup process is starting") + for servicetobackup in reversed(servicesList): + backup_services(servicetobackup, servicesDict, arguments.compose)