# 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)); }; }