backups: implement list/create and dry-run restore
All checks were successful
checks-impure / test (pull_request) Successful in 1m7s
checks / test (pull_request) Successful in 2m0s

This commit is contained in:
lassulus 2023-12-07 18:23:22 +01:00
parent 45e9ab45f7
commit cf68bd41d6
4 changed files with 64 additions and 18 deletions

View File

@ -67,11 +67,11 @@ in
clanCore.backups.providers.borgbackup = {
list = ''
ssh ${config.clan.networking.deploymentAddress} -- '
ssh ${config.clan.networking.deploymentAddress} <<EOF
${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)}
'
EOF
'';
start = ''
ssh ${config.clan.networking.deploymentAddress} -- '
@ -82,6 +82,11 @@ in
'';
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
'';
};
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;
default = ":";
description = ''
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:
machine = Machine(name=args.machine, flake_dir=args.flake)
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:

View File

@ -1,32 +1,67 @@
import argparse
from pathlib import Path
import json
import os
import subprocess
from typing import Any
from ..errors import ClanError
from ..machines.machines import Machine
from .list import list_backups
def restore_backup(
flake_dir: Path,
machine: str,
backup_data: list[dict[str, Any]],
machine: Machine,
provider: str,
backup_id: str,
archive_id: str,
service: str | 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:
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:
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:
if args.flake is None:
raise ClanError("Could not find clan flake toplevel directory")
machine = Machine(name=args.machine, flake_dir=args.flake)
backup_data = list_backups(machine=machine, provider=args.provider)
restore_backup(
Path(args.flake),
machine=args.machine,
backup_data=backup_data,
machine=machine,
provider=args.provider,
backup_id=args.backup_id,
archive_id=args.archive_id,
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"
)
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.set_defaults(func=restore_command)