From 5693e6b75d9023d78b8b46ccfdc3d6d4aae7223b Mon Sep 17 00:00:00 2001 From: Simon Bruder Date: Thu, 22 Aug 2024 22:36:47 +0200 Subject: [PATCH] restic/vm-image: Init --- machines/koyomi/services/hypervisor.nix | 10 +++ modules/restic/default.nix | 26 ++++++-- modules/restic/vm-image.nix | 84 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 modules/restic/vm-image.nix diff --git a/machines/koyomi/services/hypervisor.nix b/machines/koyomi/services/hypervisor.nix index 7f175df..6e71fd3 100644 --- a/machines/koyomi/services/hypervisor.nix +++ b/machines/koyomi/services/hypervisor.nix @@ -24,6 +24,16 @@ let }; in { + sbruder.restic = { + enable = true; + backups.vm-image = { + enable = true; + lvm.lvs = [ + "hiroshi" + ]; + }; + }; + virtualisation.libvirtd = { enable = true; qemu.package = pkgs.qemu_kvm; diff --git a/modules/restic/default.nix b/modules/restic/default.nix index 0da377e..1602690 100644 --- a/modules/restic/default.nix +++ b/modules/restic/default.nix @@ -10,7 +10,7 @@ let sftpPort = 23; repository = "sftp://${sftpTarget}:${toString sftpPort}/personal"; - mkPruneConfig = { tag, timerConfig }: { + mkPruneConfig = { tag, timerConfig, opts }: { inherit repository timerConfig; passwordFile = config.sops.secrets.restic-password.path; paths = [ ]; @@ -20,18 +20,15 @@ let ]; pruneOpts = [ "--compression auto" - "--keep-daily 7" - "--keep-monthly 12" - "--keep-weekly 5" - "--keep-yearly 10" "--tag ${tag}" "--verbose" - ]; + ] ++ opts; }; in { imports = [ ./system.nix + ./vm-image.nix ]; options.sbruder.restic = { @@ -71,6 +68,23 @@ in OnCalendar = "*-1/2-07 03:00:00"; RandomizedDelaySec = "4h"; }; + opts = [ + "--keep-daily 7" + "--keep-monthly 12" + "--keep-weekly 5" + "--keep-yearly 10" + ]; + }; + + vm-image-prune = mkPruneConfig { + tag = "vm-image"; + timerConfig = { + OnCalendar = "06:00"; + RandomizedDelaySec = "1h"; + }; + opts = [ + "--keep-last 1" + ]; }; }; }) diff --git a/modules/restic/vm-image.nix b/modules/restic/vm-image.nix new file mode 100644 index 0000000..690243a --- /dev/null +++ b/modules/restic/vm-image.nix @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2024 Simon Bruder +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +{ config, lib, pkgs, ... }: +let + cfg = config.sbruder.restic.backups.vm-image; +in +{ + options.sbruder.restic.backups.vm-image = { + enable = lib.mkEnableOption "restic vm image backup"; + timerConfig = lib.mkOption { + type = with lib.types; attrsOf str; + default = { + OnCalendar = "03:00"; + RandomizedDelaySec = "3h"; + }; + }; + lvm = { + vg = lib.mkOption { + type = lib.types.str; + default = "${config.networking.hostName}-vg"; + }; + lvs = lib.mkOption { + type = with lib.types; listOf str; + default = [ ]; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services = lib.listToAttrs (map + (lv: lib.nameValuePair "restic-backups-vm-image-${lv}" { + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + restartIfChanged = false; + + path = with pkgs; [ lvm2 restic ]; + + script = '' + set -euo pipefail + + LV_NAME=${lib.escapeShellArg lv} + FULL_LV_NAME=${lib.escapeShellArg cfg.lvm.vg}/"$LV_NAME" + SNAPSHOT_LV_NAME="restic-snapshot-$LV_NAME" + FULL_SNAPSHOT_LV_NAME=${lib.escapeShellArg cfg.lvm.vg}/"$SNAPSHOT_LV_NAME" + + lvcreate --name "$SNAPSHOT_LV_NAME" --snapshot "$FULL_LV_NAME" --permission r --ignoreactivationskip + + function cleanup { + lvchange --activate n "$FULL_SNAPSHOT_LV_NAME" + lvremove "$FULL_SNAPSHOT_LV_NAME" + } + trap cleanup EXIT INT TERM + + restic backup \ + --tag vm-image \ + --host ${config.networking.hostName}-hypervisor \ + --verbose \ + --stdin \ + --stdin-filename "$LV_NAME" \ + < "/dev/$FULL_SNAPSHOT_LV_NAME" + ''; + + environment = { + RESTIC_CACHE_DIR = "/var/cache/restic-backups-system"; # hack: reuse system backup’s directory + RESTIC_REPOSITORY_FILE = config.sops.secrets.restic-repository.path; + RESTIC_PASSWORD_FILE = config.sops.secrets.restic-password.path; + }; + + serviceConfig = { + Type = "oneshot"; + }; + }) + cfg.lvm.lvs); + + systemd.timers = (lib.listToAttrs (map + (lv: lib.nameValuePair "restic-backups-vm-image-${lv}" { + wantedBy = [ "timers.target" ]; + inherit (cfg) timerConfig; + }) + cfg.lvm.lvs)); + }; +}