diff --git a/clanModules/borgbackup.nix b/clanModules/borgbackup.nix index 933889c6..8350b3cc 100644 --- a/clanModules/borgbackup.nix +++ b/clanModules/borgbackup.nix @@ -1,4 +1,4 @@ -{ config, lib, ... }: +{ config, lib, pkgs, ... }: let cfg = config.clan.borgbackup; in @@ -19,7 +19,7 @@ in }; rsh = lib.mkOption { type = lib.types.str; - default = ""; + default = "ssh -i ${config.clanCore.secrets.borgbackup.secrets."borgbackup.ssh".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"; description = "the rsh to use for the backup"; }; @@ -55,15 +55,23 @@ in }) cfg.destinations; + clanCore.secrets.borgbackup = { + facts."borgbackup.ssh.pub" = { }; + secrets."borgbackup.ssh" = { }; + generator.path = [ pkgs.openssh pkgs.coreutils ]; + generator.script = '' + ssh-keygen -t ed25519 -N "" -f "$secrets"/borgbackup.ssh + mv "$secrets"/borgbackup.ssh.pub "$facts"/borgbackup.ssh.pub + ''; + }; + clanCore.backups.providers.borgbackup = { list = '' - ${lib.concatMapStringsSep "\n" (dest: '' - ( - export BORG_REPO=${lib.escapeShellArg dest.repo} - export BORG_RSH=${lib.escapeShellArg dest.rsh} - ${lib.getExe config.services.borgbackup.package} list - ) - '') (lib.attrValues cfg.destinations)} + ssh ${config.clan.networking.deploymentAddress} -- ' + ${lib.concatMapStringsSep "\n" (dest: '' + borg-job-${dest.name} list --json + '') (lib.attrValues cfg.destinations)} + ' ''; start = '' ssh ${config.clan.networking.deploymentAddress} -- ' diff --git a/nixosModules/clanCore/backups.nix b/nixosModules/clanCore/backups.nix index 61f2b222..31de036c 100644 --- a/nixosModules/clanCore/backups.nix +++ b/nixosModules/clanCore/backups.nix @@ -54,7 +54,7 @@ }; }; })); - default = [ ]; + default = { }; description = '' Configured backup providers which are used by this machine ''; diff --git a/pkgs/clan-cli/clan_cli/backups/list.py b/pkgs/clan-cli/clan_cli/backups/list.py index 471d3806..e3eacb43 100644 --- a/pkgs/clan-cli/clan_cli/backups/list.py +++ b/pkgs/clan-cli/clan_cli/backups/list.py @@ -1,57 +1,47 @@ import argparse -import pprint -from pathlib import Path +import json +import subprocess +from typing import Any from ..errors import ClanError +from ..machines.machines import Machine -def list_backups( - flake_dir: Path, machine: str, provider: str | None = None -) -> dict[str, dict[str, list[dict[str, str]]]]: - dummy_data = { - "testhostname": { - "borgbackup": [ - {"date": "2021-01-01T00:00:00Z", "id": "1"}, - {"date": "2022-01-01T00:00:00Z", "id": "2"}, - {"date": "2023-01-01T00:00:00Z", "id": "3"}, - ], - "restic": [ - {"date": "2021-01-01T00:00:00Z", "id": "1"}, - {"date": "2022-01-01T00:00:00Z", "id": "2"}, - {"date": "2023-01-01T00:00:00Z", "id": "3"}, - ], - }, - "another host": { - "borgbackup": [ - {"date": "2021-01-01T00:00:00Z", "id": "1"}, - {"date": "2022-01-01T00:00:00Z", "id": "2"}, - {"date": "2023-01-01T00:00:00Z", "id": "3"}, - ], - }, - } - - if provider is not None: - new_data = {} - for machine_ in dummy_data: - if provider in dummy_data[machine_]: - new_data[machine_] = {provider: dummy_data[machine_][provider]} - dummy_data = new_data - - if machine is None: - return dummy_data +def list_backups(machine: Machine, provider: str | None = None) -> list[dict[str, Any]]: + backup_scripts = json.loads( + machine.eval_nix(f"nixosConfigurations.{machine.name}.config.clanCore.backups") + ) + results = [] + if provider is None: + for provider in backup_scripts["providers"]: + proc = subprocess.run( + ["bash", "-c", backup_scripts["providers"][provider]["list"]], + stdout=subprocess.PIPE, + ) + if proc.returncode != 0: + # TODO this should be a warning, only raise exception if no providers succeed + raise ClanError("failed to list backups") + else: + results.append(proc.stdout) else: - return {machine: dummy_data[machine]} + if provider not in backup_scripts["providers"]: + raise ClanError(f"provider {provider} not found") + proc = subprocess.run( + ["bash", "-c", backup_scripts["providers"][provider]["list"]], + stdout=subprocess.PIPE, + ) + if proc.returncode != 0: + raise ClanError("failed to list backup") + else: + results.append(proc.stdout) + + return list(map(json.loads, results)) def list_command(args: argparse.Namespace) -> None: - if args.flake is None: - raise ClanError("Could not find clan flake toplevel directory") - backups = list_backups( - Path(args.flake), machine=args.machine, provider=args.provider - ) - if len(backups) > 0: - pp = pprint.PrettyPrinter(depth=4) - pp.pprint(backups) + machine = Machine(name=args.machine, flake_dir=args.flake) + backups_data = list_backups(machine=machine, provider=args.provider) + print(list(backups_data)) def register_list_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/tests/test_backups.py b/pkgs/clan-cli/tests/test_backups.py index 4129fa70..8abbc572 100644 --- a/pkgs/clan-cli/tests/test_backups.py +++ b/pkgs/clan-cli/tests/test_backups.py @@ -1,22 +1,20 @@ -import logging - +import pytest from cli import Cli from fixtures_flakes import FlakeForTest -log = logging.getLogger(__name__) - +@pytest.mark.impure def test_backups( - test_flake: FlakeForTest, + test_flake_with_core: FlakeForTest, ) -> None: cli = Cli() cli.run( [ "--flake", - str(test_flake.path), + str(test_flake_with_core.path), "backups", "list", - "testhostname", + "vm1", ] )