]> Git — Sourcephile - julm/julm-nix.git/blob - hosts/patate/backup/zfs-backup.nix
zfs: better zfs-import@ integration
[julm/julm-nix.git] / hosts / patate / backup / zfs-backup.nix
1 { pkgs, lib, hostName, ... }:
2 with builtins;
3 {
4 # Show what's happening to the user
5 systemd.services."zfs-term@" = {
6 description = "ZFS terminal for: %I";
7 unitConfig.StopWhenUnneeded = false;
8 environment.DISPLAY = ":0";
9 environment.XAUTHORITY = "/home/sevy/.Xauthority";
10 after = [ "graphical.target" ];
11 serviceConfig = {
12 Type = "simple";
13 PrivateTmp = true;
14 ExecStart = pkgs.writeShellScript "zfs-term" ''
15 DESTPOOL=$1
16 set -eux
17 ${pkgs.xterm}/bin/xterm -fg white -bg black -fa Monospace -fs 6 \
18 -title "ZFS backup to: $DESTPOOL" -e "journalctl -f -o short \
19 -u zfs-import@$DESTPOOL \
20 -u zfs-local-backup-home@$DESTPOOL"
21 '' + " %I";
22 };
23 };
24 # Prune old snapshots on the backup and send new ones
25 systemd.services."zfs-local-backup-home@" = {
26 description = "ZFS backup home, on: %I";
27 wants = [ "zfs-term@%i.service" ];
28 after = [ "zfs-import@%i.service" ];
29 requires = [ "zfs-import@%i.service" ];
30 path = lib.mkBefore [ "/run/booted-system/sw" ];
31 serviceConfig = rec {
32 Type = "oneshot";
33 PrivateTmp = true;
34 CacheDirectory = [ "zfs-usb-backup/%I" ];
35 RuntimeDirectory = [ "zfs-usb-backup/%I" ];
36 User = "sevy";
37 Group = "users";
38 SyslogIdentifier = "zfs-local-backup-home@%i";
39 ExecStartPre = "+" + pkgs.writeShellScript "zfs-local-backup-home-startPre" ''
40 DESTPOOL=$1
41 set -eux
42 if zpool status "$DESTPOOL"; then
43 zfs allow ${User} bookmark,hold,mount,send ${hostName}/home
44 zfs allow ${User} bookmark,create,destroy,load-key,mount,mountpoint,receive,rollback,snapshot "$DESTPOOL"/${User}
45 zpool scrub -p "$DESTPOOL" || true
46 fi
47 '' + " %I";
48 ExecStart = pkgs.writeShellScript "zfs-local-backup-home" ''
49 set -eu
50 DESTPOOL=$1
51 # sanoid is quite conservative:
52 # by setting hourly=24, a snapshot must be >24 hours old
53 # and there must been >24 total hourly snapshots,
54 # or nothing is pruned.
55 install -D -m 400 /dev/stdin /tmp/sanoid/sanoid.conf <<EOF
56 [template_remote]
57 autoprune=true
58 autosnap=false
59 process_children_only=false
60
61 [$DESTPOOL/${User}/backup/${hostName}/home]
62 hourly=12
63 daily=31
64 monthly=6
65 recursive=true
66 use_template=remote
67 EOF
68 set -x
69 ${pkgs.sanoid}/bin/sanoid \
70 --cache-dir /var/cache/zfs-usb-backup/"$DESTPOOL" \
71 --configdir /tmp/sanoid \
72 --prune-snapshots \
73 --run-dir /run/zfs-usb-backup/"$DESTPOOL" \
74 --verbose
75
76 for dataset in ${hostName}/home; do
77 ${pkgs.sanoid}/bin/syncoid \
78 --create-bookmark \
79 --exclude "home/Downloads" \
80 --exclude "home/Videos" \
81 --force-delete \
82 --no-privilege-elevation \
83 --no-sync-snap \
84 --recursive \
85 --recvoptions "" \
86 --sendoptions raw \
87 --skip-parent \
88 "$dataset" \
89 "$DESTPOOL"/${User}/backup/"$dataset"
90 done
91 '' + " %I";
92 ExecStartPost = "+" + pkgs.writeShellScript "zfs-local-backup-home-startPost" ''
93 DESTPOOL=$1
94 set -eux
95 # Only if the zpool still exists to avoid uninterruptible hanging
96 if zpool status -v "$DESTPOOL"; then
97 # Scrub the zpool 1 minute (in the background)
98 zpool scrub "$DESTPOOL"
99 sleep 60
100 fi
101 while zpool status -v "$DESTPOOL"; do
102 zpool scrub -p "$DESTPOOL" || true
103 sleep 20
104 # Export the zpool (to avoid a forced import later on)
105 zpool export "$DESTPOOL" || true
106 done
107 systemctl --no-block stop zfs-term@"$DESTPOOL"
108 '' + " %I";
109 };
110 };
111 programs.bash.interactiveShellInit = ''
112 mount-zfs-backup () {
113 (
114 set -eux
115 zpool="$1"
116 zpool status "$zpool" 2>/dev/null ||
117 sudo zpool import -d /dev/disk/by-id/ "$zpool"
118 trap "sudo zpool export $zpool" EXIT
119 zfs list -rH -t filesystem -o mounted,mountpoint,name "$zpool"/"$USER"/backup |
120 grep "^no\\s*/" | cut -f 3 | xargs -ortL1 sudo zfs mount -Olv || true
121 ${pkgs.mate.caja-with-extensions}/bin/caja --browser /mnt/"$zpool"/"$USER"/backup
122 )
123 }
124 '';
125 }