Merge pull request 'backups: implement list the easy way' (#617) from lassulus-backups2 into main

This commit is contained in:
clan-bot
2023-12-07 17:25:56 +00:00
4 changed files with 64 additions and 18 deletions

View File

@@ -67,11 +67,11 @@ in
clanCore.backups.providers.borgbackup = { clanCore.backups.providers.borgbackup = {
list = '' list = ''
ssh ${config.clan.networking.deploymentAddress} -- ' ssh ${config.clan.networking.deploymentAddress} <<EOF
${lib.concatMapStringsSep "\n" (dest: '' ${lib.concatMapStringsSep "\n" (dest: ''
borg-job-${dest.name} list --json borg-job-${dest.name} list --json | jq -r '. + {"job-name": "${dest.name}"}'
'') (lib.attrValues cfg.destinations)} '') (lib.attrValues cfg.destinations)}
' EOF
''; '';
start = '' start = ''
ssh ${config.clan.networking.deploymentAddress} -- ' ssh ${config.clan.networking.deploymentAddress} -- '
@@ -82,6 +82,11 @@ in
''; '';
restore = '' restore = ''
ssh ${config.clan.networking.deploymentAddress} -- LOCATION="$LOCATION" ARCHIVE="$ARCHIVE_ID" JOB="$JOB" '
set -efux
cd /
borg-job-"$JOB" extract --list --dry-run "$LOCATION"::"$ARCHIVE"
'
''; '';
}; };
}; };

View File

@@ -11,13 +11,19 @@
Folder where state resides in Folder where state resides in
''; '';
}; };
restoreScript = lib.mkOption { preRestoreScript = lib.mkOption {
type = lib.types.str;
default = ":";
description = ''
script to run before restoring the state dir from a backup
'';
};
postRestoreScript = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = ":"; default = ":";
description = '' description = ''
script to restore the service after the state dir was restored from a backup script to restore the service after the state dir was restored from a backup
''; '';
}; };
}; };
})); }));

View File

@@ -41,7 +41,7 @@ def list_backups(machine: Machine, provider: str | None = None) -> list[dict[str
def list_command(args: argparse.Namespace) -> None: def list_command(args: argparse.Namespace) -> None:
machine = Machine(name=args.machine, flake_dir=args.flake) machine = Machine(name=args.machine, flake_dir=args.flake)
backups_data = list_backups(machine=machine, provider=args.provider) backups_data = list_backups(machine=machine, provider=args.provider)
print(list(backups_data)) print(json.dumps(list(backups_data)))
def register_list_parser(parser: argparse.ArgumentParser) -> None: def register_list_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -1,32 +1,67 @@
import argparse import argparse
from pathlib import Path import json
import os
import subprocess
from typing import Any
from ..errors import ClanError from ..errors import ClanError
from ..machines.machines import Machine
from .list import list_backups
def restore_backup( def restore_backup(
flake_dir: Path, backup_data: list[dict[str, Any]],
machine: str, machine: Machine,
provider: str, provider: str,
backup_id: str, archive_id: str,
service: str | None = None, service: str | None = None,
) -> None: ) -> None:
backup_scripts = json.loads(
machine.eval_nix(f"nixosConfigurations.{machine.name}.config.clanCore.backups")
)
backup_folders = json.loads(
machine.eval_nix(f"nixosConfigurations.{machine.name}.config.clanCore.state")
)
if service is None: if service is None:
print("would restore backup", machine, provider, backup_id) for backup in backup_data:
for archive in backup["archives"]:
if archive["archive"] == archive_id:
env = os.environ.copy()
env["ARCHIVE_ID"] = archive_id
env["LOCATION"] = backup["repository"]["location"]
env["JOB"] = backup["job-name"]
proc = subprocess.run(
[
"bash",
"-c",
backup_scripts["providers"][provider]["restore"],
],
stdout=subprocess.PIPE,
env=env,
)
if proc.returncode != 0:
# TODO this should be a warning, only raise exception if no providers succeed
raise ClanError("failed to restore backup")
else: else:
print( print(
"would restore backup", machine, provider, backup_id, "of service:", service "would restore backup",
machine,
provider,
archive_id,
"of service:",
service,
) )
print(backup_folders)
def restore_command(args: argparse.Namespace) -> None: def restore_command(args: argparse.Namespace) -> None:
if args.flake is None: machine = Machine(name=args.machine, flake_dir=args.flake)
raise ClanError("Could not find clan flake toplevel directory") backup_data = list_backups(machine=machine, provider=args.provider)
restore_backup( restore_backup(
Path(args.flake), backup_data=backup_data,
machine=args.machine, machine=machine,
provider=args.provider, provider=args.provider,
backup_id=args.backup_id, archive_id=args.archive_id,
service=args.service, service=args.service,
) )
@@ -36,6 +71,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("backup_id", type=str, help="id of the backup to restore") parser.add_argument("archive_id", type=str, help="id 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)