diff --git a/checks/backups/flake-module.nix b/checks/backups/flake-module.nix index 0b28c029..ac8317b8 100644 --- a/checks/backups/flake-module.nix +++ b/checks/backups/flake-module.nix @@ -114,20 +114,18 @@ machine.succeed("echo testing > /var/test-backups/somefile") # 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.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2") # list 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 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" ''; } { inherit pkgs self; }; diff --git a/clanModules/borgbackup.nix b/clanModules/borgbackup.nix index b6d4d99d..419e8dc0 100644 --- a/clanModules/borgbackup.nix +++ b/clanModules/borgbackup.nix @@ -92,10 +92,14 @@ in clanCore.backups.providers.borgbackup = { # TODO list needs to run locally or on the remote machine + list = '' + set -efu # we need yes here to skip the changed url verification ${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)} ''; create = '' @@ -108,7 +112,7 @@ in set -efu cd / 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[@]}" ''; }; }; diff --git a/pkgs/clan-cli/clan_cli/backups/list.py b/pkgs/clan-cli/clan_cli/backups/list.py index f890d22b..272f50a4 100644 --- a/pkgs/clan-cli/clan_cli/backups/list.py +++ b/pkgs/clan-cli/clan_cli/backups/list.py @@ -9,11 +9,8 @@ from ..machines.machines import Machine @dataclass class Backup: - archive_id: str - date: str - provider: str - remote_path: str - job_name: str + name: str + job_name: str | None = None 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: # 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: parsed_json = json.loads(proc.stdout) - # TODO move borg specific code to borgbackup.nix - for archive in parsed_json["archives"]: - backup = Backup( - archive_id=archive["archive"], - date=archive["time"], - provider=provider, - remote_path=parsed_json["repository"]["location"], - job_name=parsed_json["job-name"], + for archive in parsed_json: + results.append( + Backup(name=archive["name"], job_name=archive.get("job_name")) ) - results.append(backup) return results @@ -59,7 +51,7 @@ def list_command(args: argparse.Namespace) -> None: machine = Machine(name=args.machine, flake=args.flake) backups = list_backups(machine=machine, provider=args.provider) for backup in backups: - print(backup.archive_id) + print(backup.name) def register_list_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/clan_cli/backups/restore.py b/pkgs/clan-cli/clan_cli/backups/restore.py index 3a24a1f9..4f49d38b 100644 --- a/pkgs/clan-cli/clan_cli/backups/restore.py +++ b/pkgs/clan-cli/clan_cli/backups/restore.py @@ -15,11 +15,12 @@ def restore_service( backup_folders = json.loads(machine.eval_nix("config.clanCore.state")) folders = backup_folders[service]["folders"] env = os.environ.copy() - env["ARCHIVE_ID"] = backup.archive_id - env["LOCATION"] = backup.remote_path - env["JOB"] = backup.job_name + env["NAME"] = backup.name env["FOLDERS"] = ":".join(folders) + if backup.job_name is not None: + env["JOB_NAME"] = backup.job_name + proc = machine.target_host.run( [ "bash", @@ -67,19 +68,25 @@ def restore_backup( machine: Machine, backups: list[Backup], provider: str, - archive_id: str, + name: str, service: str | None = None, ) -> None: if service is None: for backup in backups: - if backup.archive_id == archive_id: + if backup.name == name: backup_folders = json.loads(machine.eval_nix("config.clanCore.state")) for _service in backup_folders: restore_service(machine, backup, provider, _service) + break + else: + raise ClanError(f"backup {name} not found") else: for backup in backups: - if backup.archive_id == archive_id: + if backup.name == name: restore_service(machine, backup, provider, service) + break + else: + raise ClanError(f"backup {name} not found") def restore_command(args: argparse.Namespace) -> None: @@ -89,7 +96,7 @@ def restore_command(args: argparse.Namespace) -> None: machine=machine, backups=backups, provider=args.provider, - archive_id=args.archive_id, + name=args.name, 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" ) 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.set_defaults(func=restore_command)