1
0
forked from clan/clan-core

make backup provider more generic

This commit is contained in:
Jörg Thalheim 2024-03-19 17:25:31 +01:00 committed by Mic92
parent 7c4c6c07af
commit 9383e41d68
4 changed files with 33 additions and 32 deletions

View File

@ -114,20 +114,18 @@
machine.succeed("echo testing > /var/test-backups/somefile") machine.succeed("echo testing > /var/test-backups/somefile")
# create # create
machine.succeed("ping -c1 machine >&2")
machine.succeed("ssh -i /etc/secrets/borgbackup.ssh -v machine hostname >&2")
machine.succeed("systemctl status >&2")
machine.succeed("systemctl start borgbackup-job-test-backup")
machine.succeed("clan --debug --flake ${self} backups create test-backup") machine.succeed("clan --debug --flake ${self} backups create test-backup")
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2") machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
# list # list
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"] backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
assert backup_id in machine.succeed("clan --debug --flake ${self} backups list test-backup"), "backup not listed" out = machine.succeed("clan --debug --flake ${self} backups list test-backup")
print(out)
assert backup_id in out, f"backup {backup_id} not found in {out}"
# restore # restore
machine.succeed("rm -f /var/test-backups/somefile") machine.succeed("rm -f /var/test-backups/somefile")
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup borgbackup {backup_id}") machine.succeed(f"clan --debug --flake ${self} backups restore test-backup borgbackup borg@machine:.::{backup_id} >&2")
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed" assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
''; '';
} { inherit pkgs self; }; } { inherit pkgs self; };

View File

@ -92,10 +92,14 @@ in
clanCore.backups.providers.borgbackup = { clanCore.backups.providers.borgbackup = {
# TODO list needs to run locally or on the remote machine # TODO list needs to run locally or on the remote machine
list = '' list = ''
set -efu
# we need yes here to skip the changed url verification # we need yes here to skip the changed url verification
${lib.concatMapStringsSep "\n" ( ${lib.concatMapStringsSep "\n" (
dest: ''yes y | borg-job-${dest.name} list --json | jq -r '. + {"job-name": "${dest.name}"}' '' dest: ''
yes y | borg-job-${dest.name} list --json | jq '{"backups": [.archives[] | {"name": ("${dest.repo}::" + .name), "job_name": "${dest.name}"}]}'
''
) (lib.attrValues cfg.destinations)} ) (lib.attrValues cfg.destinations)}
''; '';
create = '' create = ''
@ -108,7 +112,7 @@ in
set -efu set -efu
cd / cd /
IFS=';' read -ra FOLDER <<< "$FOLDERS" IFS=';' read -ra FOLDER <<< "$FOLDERS"
yes y | borg-job-"$JOB" extract --list "$LOCATION"::"$ARCHIVE_ID" "''${FOLDER[@]}" yes y | borg-job-"$JOB_NAME" extract --list "$NAME" "''${FOLDER[@]}"
''; '';
}; };
}; };

View File

@ -9,11 +9,8 @@ from ..machines.machines import Machine
@dataclass @dataclass
class Backup: class Backup:
archive_id: str name: str
date: str job_name: str | None = None
provider: str
remote_path: str
job_name: str
def list_provider(machine: Machine, provider: str) -> list[Backup]: def list_provider(machine: Machine, provider: str) -> list[Backup]:
@ -26,19 +23,14 @@ def list_provider(machine: Machine, provider: str) -> list[Backup]:
) )
if proc.returncode != 0: if proc.returncode != 0:
# TODO this should be a warning, only raise exception if no providers succeed # TODO this should be a warning, only raise exception if no providers succeed
ClanError(f"failed to list backups for provider {provider}") msg = f"failed to list backups for provider {provider}: {proc.stdout}"
raise ClanError(msg)
else: else:
parsed_json = json.loads(proc.stdout) parsed_json = json.loads(proc.stdout)
# TODO move borg specific code to borgbackup.nix for archive in parsed_json:
for archive in parsed_json["archives"]: results.append(
backup = Backup( Backup(name=archive["name"], job_name=archive.get("job_name"))
archive_id=archive["archive"],
date=archive["time"],
provider=provider,
remote_path=parsed_json["repository"]["location"],
job_name=parsed_json["job-name"],
) )
results.append(backup)
return results return results
@ -59,7 +51,7 @@ def list_command(args: argparse.Namespace) -> None:
machine = Machine(name=args.machine, flake=args.flake) machine = Machine(name=args.machine, flake=args.flake)
backups = list_backups(machine=machine, provider=args.provider) backups = list_backups(machine=machine, provider=args.provider)
for backup in backups: for backup in backups:
print(backup.archive_id) print(backup.name)
def register_list_parser(parser: argparse.ArgumentParser) -> None: def register_list_parser(parser: argparse.ArgumentParser) -> None:

View File

@ -15,11 +15,12 @@ def restore_service(
backup_folders = json.loads(machine.eval_nix("config.clanCore.state")) backup_folders = json.loads(machine.eval_nix("config.clanCore.state"))
folders = backup_folders[service]["folders"] folders = backup_folders[service]["folders"]
env = os.environ.copy() env = os.environ.copy()
env["ARCHIVE_ID"] = backup.archive_id env["NAME"] = backup.name
env["LOCATION"] = backup.remote_path
env["JOB"] = backup.job_name
env["FOLDERS"] = ":".join(folders) env["FOLDERS"] = ":".join(folders)
if backup.job_name is not None:
env["JOB_NAME"] = backup.job_name
proc = machine.target_host.run( proc = machine.target_host.run(
[ [
"bash", "bash",
@ -67,19 +68,25 @@ def restore_backup(
machine: Machine, machine: Machine,
backups: list[Backup], backups: list[Backup],
provider: str, provider: str,
archive_id: str, name: str,
service: str | None = None, service: str | None = None,
) -> None: ) -> None:
if service is None: if service is None:
for backup in backups: for backup in backups:
if backup.archive_id == archive_id: if backup.name == name:
backup_folders = json.loads(machine.eval_nix("config.clanCore.state")) backup_folders = json.loads(machine.eval_nix("config.clanCore.state"))
for _service in backup_folders: for _service in backup_folders:
restore_service(machine, backup, provider, _service) restore_service(machine, backup, provider, _service)
break
else:
raise ClanError(f"backup {name} not found")
else: else:
for backup in backups: for backup in backups:
if backup.archive_id == archive_id: if backup.name == name:
restore_service(machine, backup, provider, service) restore_service(machine, backup, provider, service)
break
else:
raise ClanError(f"backup {name} not found")
def restore_command(args: argparse.Namespace) -> None: def restore_command(args: argparse.Namespace) -> None:
@ -89,7 +96,7 @@ def restore_command(args: argparse.Namespace) -> None:
machine=machine, machine=machine,
backups=backups, backups=backups,
provider=args.provider, provider=args.provider,
archive_id=args.archive_id, name=args.name,
service=args.service, service=args.service,
) )
@ -99,6 +106,6 @@ def register_restore_parser(parser: argparse.ArgumentParser) -> None:
"machine", type=str, help="machine in the flake to create backups of" "machine", type=str, help="machine in the flake to create backups of"
) )
parser.add_argument("provider", type=str, help="backup provider to use") parser.add_argument("provider", type=str, help="backup provider to use")
parser.add_argument("archive_id", type=str, help="id of the backup to restore") parser.add_argument("name", type=str, help="Name of the backup to restore")
parser.add_argument("--service", type=str, help="name of the service to restore") parser.add_argument("--service", type=str, help="name of the service to restore")
parser.set_defaults(func=restore_command) parser.set_defaults(func=restore_command)