{ config, pkgs, lib, ... }: with lib; let cfg = config.services.backup; toConf = pre: settings: let mkLine = pre: key: val: if builtins.isAttrs val then toConf key val else (if (builtins.stringLength pre > 0) then "${pre}\t" else "") + "${key}\t${builtins.toString val}"; in builtins.concatStringsSep "\n" (builtins.attrValues (builtins.mapAttrs (mkLine pre) settings)); in { options.services.backup = { root = mkOption { type = types.path; description = "The root directory storing the local backups."; default = "${config.xdg.dataHome}/backup/"; defaultText = "$XDG_DATA_HOME/backup/"; }; paths = mkOption { type = types.listOf types.path; description = "List of files and directories to backup."; default = [ ]; }; schedule = let scheduleEntry = types.submodule { options = { name = mkOption { type = types.str; description = "Name of the backup timer."; }; retain = mkOption { type = types.ints.unsigned; description = "The number of backups to keep at any time."; }; when = mkOption { type = types.str; description = "How often to run the backup job (systemd timer format)."; }; }; }; in mkOption { type = types.listOf scheduleEntry; description = '' A list of scheduled timers. Each entry gives information on how often to run a backup (systemd timer format) and how many backups to retain. They are assumed to be ordered from the more to the less frequent. Default is set to a sensible configuration. ''; default = [ { name = "daily"; retain = 7; when = "19:40"; } { name = "weekly"; retain = 4; when = "Sun 19:20"; } { name = "monthly"; retain = 3; when = "*-*-01 19:00"; } ]; defaultText = '' [ { name = "daily"; retain = 7; when = "19:40"; } { name = "weekly"; retain = 4; when = "Sun 19:20"; } { name = "monthly"; retain = 3; when = "*-*-01 19:00"; } ] ''; }; remote = mkOption { type = types.submodule { options = { backends = mkOption { type = types.listOf types.str; description = "Name of the backup timer."; default = [ ]; }; when = mkOption { type = types.str; description = "How often to run the backup job (systemd timer format)."; default = "20:00"; }; }; }; description = "Remote backup configuration"; default = { }; }; }; config = let rsnapshot-conf = { version = "1.2"; settings = { cmd_cp = "${pkgs.coreutils}/bin/cp"; cmd_du = "${pkgs.coreutils}/bin/du"; cmd_logger = "${pkgs.logger}/bin/logger"; cmd_rm = "${pkgs.coreutils}/bin/cp"; cmd_rsync = "${pkgs.rsync}/bin/rsync"; lockfile = "${config.xdg.cacheHome}/rsnapshot.pid"; loglevel = 3; verbose = 2; backup = builtins.listToAttrs (builtins.map (p: { name = p; value = "./"; }) cfg.paths); retain = builtins.listToAttrs (builtins.map (s: { name = s.name; value = s.retain; }) cfg.schedule); }; }; back-me-up = let target = if builtins.length cfg.schedule > 0 then (builtins.head cfg.schedule).name else "no-target"; backend = b : '' rclone copy --no-traverse "$ARCHIVE" ${b}:backup/ rclone delete --min-age 3d ${b}:backup/ --include "$HOSTNAME-*.tar.gz" ''; backends = builtins.concatStringsSep "\n" (builtins.map backend cfg.remote.backends); in pkgs.writeShellApplication { name = "back-me-up"; runtimeInputs = with pkgs; [ gnutar rclone ]; text = '' ARCHIVE="$HOSTNAME-$(date '+%y%m%d%H%M%S').tar.gz" TARGET="${target}.0" cd ${cfg.root} if [ -d "$TARGET" ] ; then tar -czvf "$ARCHIVE" -C $TARGET . ${backends} rm -f "$ARCHIVE" fi ''; }; in mkIf (builtins.length cfg.paths > 0) { home.packages = [ pkgs.rsnapshot back-me-up ]; xdg.configFile.rsnapshot = { target = "rsnapshot/rsnapshot.conf"; text = '' config_version ${rsnapshot-conf.version} snapshot_root ${cfg.root} '' + toConf "" rsnapshot-conf.settings; }; # Automate local backups with rsnapshot and remote # backups with rclone, via systemd. # See https://wiki.archlinux.org/title/Rsnapshot#Automation systemd.user.services = { "rsnapshot@" = let conf = "${config.home.homeDirectory}/${config.xdg.configFile.rsnapshot.target}"; in { Unit = { Description = "A %I rsnapshot backup."; }; Service = { Type = "oneshot"; Nice = 19; IOSchedulingClass = "idle"; ExecStart = "${pkgs.rsnapshot}/bin/rsnapshot -c ${conf} %I"; }; }; "back-me-up" = { Unit = { Description = "A backup script to various services (using rclone)."; RefuseManualStart = true; }; Service = { Type = "oneshot"; Nice = 19; IOSchedulingClass = "idle"; ExecStart = "${back-me-up}/bin/back-me-up"; }; }; }; systemd.user.timers = let toTimer = s: { Unit = { Description = "A ${s.name} rsnapshot backup timer."; }; Timer = { OnCalendar = s.when; Persistent = true; Unit = "rsnapshot@${s.name}.service"; }; Install = { WantedBy = [ "timers.target" ]; }; }; rsnapshot-timers = builtins.listToAttrs (builtins.map (s: { name = "rsnapshot-${s.name}"; value = toTimer s; }) cfg.schedule); rclone-timer = { "back-me-up" = { Unit = { Description = "A remote backup timer calling the corresponding service."; }; Timer = { OnCalendar = cfg.remote.when; Persistent = true; Unit = "back-me-up.service"; }; Install = { WantedBy = [ "timers.target" ]; }; }; }; in rsnapshot-timers // rclone-timer; }; }