1{ 2 pkgs, 3 config, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.custom; 10in 11{ 12 options.custom.external-hdd-backup = { 13 enable = lib.mkEnableOption "laptop"; 14 disk = lib.mkOption { 15 type = lib.types.str; 16 default = "/dev/disk/by-label/external-hdd"; 17 }; 18 mountdir = lib.mkOption { 19 type = lib.types.str; 20 default = "/media"; 21 }; 22 mountname = lib.mkOption { 23 type = lib.types.str; 24 default = "external-hdd"; 25 }; 26 }; 27 28 config = lib.mkIf cfg.external-hdd-backup.enable { 29 systemd.services.backup = { 30 description = "Backup service"; 31 # NB udisks isn't viable as non-root due to: 32 # Error creating textual authentication agent: Error opening current controlling terminal for the process (`/dev/tty'): No such device or address (polkit-error-quark, 0) 33 # Error mounting /dev/sda1: GDBus.Error:org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain: Not authorized to perform operation 34 # And in order to communicate with GUI prompts, e.g. yad, we need to run as user 35 # udisks is still use for on-demand mountin, but we'll use the autofs for mounting the backup disk 36 script = 37 let 38 backup = pkgs.writeShellScript "backup.sh" '' 39 # TODO make nixos module with options 40 DISK="${cfg.backup.disk}" 41 LAST_RUN_FILE="$HOME/.cache/last_backup" 42 43 if [ -f "$LAST_RUN_FILE" ] && [ "$(( $(date +%s) - $(date +%s -r "$LAST_RUN_FILE") ))" -lt 86400 ]; then 44 echo "<24hrs" 45 exit 0 46 fi 47 48 # if no external-hdd 49 if [ ! -e $DISK ]; then 50 echo "no $DISK" 51 exit 0 52 fi 53 54 export DISPLAY=:0 55 ${pkgs.xorg.xhost}/bin/xhost +local:${config.custom.username} 56 export GTK_R2_FILES=$HOME/.gtkrc-2.0 57 timeout 60 ${pkgs.yad}/bin/yad --question --title "backup" --text "Backup now? Will autostart in 60s." 58 prompt_status=$? 59 ${pkgs.xorg.xhost}/bin/xhost -local:${config.custom.username} 60 # if not success or timeout 61 if [ ! $prompt_status -eq 0 -a ! $prompt_status -eq 124 ]; then 62 echo "backup cancelled" 63 ${pkgs.libnotify}/bin/notify-send "backup cancelled" 64 exit 0 65 fi 66 67 DIR="${cfg.backup.mountdir}/${cfg.backup.mountname}" 68 cd "$DIR" 69 TEST_DIR=`${pkgs.util-linux}/bin/findmnt -nr -o target -S $DISK` 70 status=$? 71 if [ ! $status -eq 0 ]; then 72 echo "backup failed to find mount" 73 ${pkgs.libnotify}/bin/notify-send "backup failed to find mount" 74 exit $status 75 fi 76 if [ "$DIR" != "$TEST_DIR" ]; then 77 echo "backup disk mounted at unexpected path: $TEST_DIR" 78 ${pkgs.libnotify}/bin/notify-send "backup disk mounted at unexpected path: $TEST_DIR" 79 exit 1 80 fi 81 ${pkgs.libnotify}/bin/notify-send "backup starting" 82 ${pkgs.rsync}/bin/rsync -va --exclude={".cache",".local/share/Steam/"} ~/ $DIR/home/ deleteafter 83 status=$? 84 if [ $status -eq 0 ]; then 85 touch "$LAST_RUN_FILE" 86 echo "backup finished" 87 ${pkgs.libnotify}/bin/notify-send "backup finished" 88 else 89 echo "backup failed" 90 ${pkgs.libnotify}/bin/notify-send "backup failed" 91 fi 92 exit $status 93 ''; 94 in 95 "${backup}"; 96 serviceConfig = { 97 Type = "oneshot"; 98 User = config.custom.username; 99 }; 100 # trigger on wake 101 wantedBy = [ 102 "suspend.target" 103 "hibernate.target" 104 "hybrid-sleep.target" 105 "suspend-then-hibernate.target" 106 ]; 107 environment.DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/user/1000/bus"; 108 }; 109 # trigger backup on hard drive connection 110 services.udev.extraRules = 111 # NB could check device label with a trigger script that checks $0 and `RUN+="${trigger} /media/$env{ID_FS_LABEL}"` 112 # but we just assume the Seagate Expansion Desk is the same as /dev/disk/by-label/external-hdd 113 # UDEV has crap support for detecting devices 114 '' 115 ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]*[0-9]*", ATTRS{model}=="Expansion Desk ", ATTRS{vendor}=="Seagate ", TAG+="systemd", ENV{SYSTEMD_WANTS}+="backup" 116 ''; 117 services.autofs = { 118 enable = true; 119 autoMaster = 120 let 121 map = pkgs.writeText "auto.media" '' 122 ${cfg.backup.mountname} -fstype=auto :${cfg.backup.disk} 123 ''; 124 in 125 '' 126 ${cfg.backup.mountdir} file,sun:${map} -t 60 127 ''; 128 }; 129 }; 130}