]>
Commit | Line | Data |
---|---|---|
ab8f306d | 1 | { lib, pkgs, config, ... }: |
285380fe IB |
2 | let |
3 | partModule = lib.types.submodule { | |
4 | options = { | |
5 | remote_folder = lib.mkOption { | |
6 | type = lib.types.path; | |
7 | description = '' | |
8 | Path to backup | |
9 | ''; | |
10 | }; | |
11 | exclude_from = lib.mkOption { | |
12 | type = lib.types.listOf lib.types.path; | |
13 | default = []; | |
14 | description = '' | |
15 | Paths to exclude from the backup | |
16 | ''; | |
17 | }; | |
18 | files_from = lib.mkOption { | |
19 | type = lib.types.listOf lib.types.path; | |
20 | default = []; | |
21 | description = '' | |
22 | Paths to take for the backup | |
23 | (if empty: whole folder minus exclude_from) | |
24 | ''; | |
25 | }; | |
26 | args = lib.mkOption { | |
27 | type = lib.types.nullOr lib.types.str; | |
28 | default = null; | |
29 | description = '' | |
30 | additional arguments for rsync | |
31 | ''; | |
32 | }; | |
33 | }; | |
34 | }; | |
35 | profileModule = lib.types.submodule { | |
36 | options = { | |
37 | keep = lib.mkOption { | |
38 | type = lib.types.int; | |
39 | default = 7; | |
40 | description = '' | |
41 | Number of backups to keep | |
42 | ''; | |
43 | }; | |
46b7e627 IB |
44 | check_command = lib.mkOption { |
45 | type = lib.types.str; | |
46 | default = "backup"; | |
47 | description = '' | |
48 | command to check if backup needs to be done | |
49 | ''; | |
50 | }; | |
285380fe IB |
51 | login = lib.mkOption { |
52 | type = lib.types.str; | |
53 | description = '' | |
54 | login to connect to | |
55 | ''; | |
56 | }; | |
57 | host = lib.mkOption { | |
58 | type = lib.types.str; | |
59 | description = '' | |
60 | host to connect to | |
61 | ''; | |
62 | }; | |
63 | port = lib.mkOption { | |
64 | type = lib.types.str; | |
65 | default = "22"; | |
66 | description = '' | |
67 | port to connect to | |
68 | ''; | |
69 | }; | |
70 | host_key = lib.mkOption { | |
71 | type = lib.types.str; | |
72 | description = '' | |
73 | Host key to use as known host | |
74 | ''; | |
75 | }; | |
76 | host_key_type = lib.mkOption { | |
77 | type = lib.types.str; | |
78 | description = '' | |
79 | Host key type | |
80 | ''; | |
81 | }; | |
82 | parts = lib.mkOption { | |
83 | type = lib.types.attrsOf partModule; | |
84 | description = '' | |
85 | folders to backup in the host | |
86 | ''; | |
87 | }; | |
88 | }; | |
89 | }; | |
90 | cfg = config.services.rsyncBackup; | |
91 | ||
92 | ssh_key = config.secrets.fullPaths."rsync_backup/identity"; | |
93 | ||
739d28ea | 94 | backup_head = '' |
285380fe IB |
95 | #!${pkgs.stdenv.shell} |
96 | EXCL_FROM=`mktemp` | |
97 | FILES_FROM=`mktemp` | |
98 | TMP_STDERR=`mktemp` | |
99 | ||
100 | on_exit() { | |
285380fe | 101 | if [ -s "$TMP_STDERR" ]; then |
739d28ea | 102 | cat "$TMP_STDERR" |
285380fe | 103 | fi |
285380fe IB |
104 | rm -f $TMP_STDERR $EXCL_FROM $FILES_FROM |
105 | } | |
106 | ||
107 | trap "on_exit" EXIT | |
108 | ||
109 | exec 2> "$TMP_STDERR" | |
110 | exec < /dev/null | |
111 | ||
112 | set -e | |
113 | ''; | |
114 | ||
115 | backup_profile = name: profile: builtins.concatStringsSep "\n" ( | |
116 | [(backup_profile_head name profile)] | |
117 | ++ lib.mapAttrsToList (backup_part name) profile.parts | |
118 | ++ [(backup_profile_tail name profile)]); | |
119 | ||
120 | backup_profile_head = name: profile: '' | |
121 | ##### ${name} ##### | |
122 | PORT="${profile.port}" | |
123 | DEST="${profile.login}@${profile.host}" | |
124 | BASE="${cfg.mountpoint}/${name}" | |
125 | OLD_BAK_BASE=$BASE/older/j | |
126 | BAK_BASE=''${OLD_BAK_BASE}0 | |
127 | RSYNC_OUTPUT=$BASE/rsync_output | |
128 | NBR=${builtins.toString profile.keep} | |
129 | ||
130 | if ! ssh \ | |
131 | -o PreferredAuthentications=publickey \ | |
132 | -o StrictHostKeyChecking=yes \ | |
133 | -o ClearAllForwardings=yes \ | |
0e118a8c IB |
134 | -o UserKnownHostsFile=/dev/null \ |
135 | -o CheckHostIP=no \ | |
285380fe IB |
136 | -p $PORT \ |
137 | -i ${ssh_key} \ | |
46b7e627 | 138 | $DEST ${profile.check_command}; then |
285380fe IB |
139 | echo "Fichier de verrouillage backup sur $DEST ou impossible de se connecter" >&2 |
140 | skip=$DEST | |
141 | fi | |
142 | ||
143 | rm -rf ''${OLD_BAK_BASE}''${NBR} | |
144 | for j in `seq -w $(($NBR-1)) -1 0`; do | |
145 | [ ! -d ''${OLD_BAK_BASE}$j ] && continue | |
146 | mv ''${OLD_BAK_BASE}$j ''${OLD_BAK_BASE}$(($j+1)) | |
147 | done | |
148 | mkdir $BAK_BASE | |
149 | mv $RSYNC_OUTPUT $BAK_BASE | |
150 | mkdir $RSYNC_OUTPUT | |
151 | ||
152 | if [ "$skip" != "$DEST" ]; then | |
153 | ''; | |
154 | ||
155 | backup_profile_tail = name: profile: '' | |
0e118a8c | 156 | ssh -o UserKnownHostsFile=/dev/null -o CheckHostIP=no -i ${ssh_key} -p $PORT $DEST sh -c "date > .cache/last_backup" |
285380fe IB |
157 | fi # [ "$skip" != "$DEST" ] |
158 | ##### End ${name} ##### | |
159 | ''; | |
160 | ||
161 | backup_part = profile_name: part_name: part: '' | |
162 | ### ${profile_name} ${part_name} ### | |
163 | LOCAL="${part_name}" | |
164 | REMOTE="${part.remote_folder}" | |
165 | ||
166 | if [ ! -d "$BASE/$LOCAL" ]; then | |
167 | mkdir $BASE/$LOCAL | |
168 | fi | |
169 | cd $BASE/$LOCAL | |
170 | cat > $EXCL_FROM <<EOF | |
171 | ${builtins.concatStringsSep "\n" part.exclude_from} | |
172 | EOF | |
173 | cat > $FILES_FROM <<EOF | |
174 | ${builtins.concatStringsSep "\n" part.files_from} | |
175 | EOF | |
176 | ||
177 | OUT=$RSYNC_OUTPUT/$LOCAL | |
e920f02d | 178 | ${pkgs.rsync}/bin/rsync --new-compress -XAavbr --fake-super -e "ssh -o UserKnownHostsFile=/dev/null -o CheckHostIP=no -i ${ssh_key} -p $PORT" --numeric-ids --delete \ |
285380fe IB |
179 | --backup-dir=$BAK_BASE/$LOCAL \${ |
180 | lib.optionalString (part.args != null) "\n ${part.args} \\"}${ | |
181 | lib.optionalString (builtins.length part.exclude_from > 0) "\n --exclude-from=$EXCL_FROM \\"}${ | |
182 | lib.optionalString (builtins.length part.files_from > 0) "\n --files-from=$FILES_FROM \\"} | |
183 | $DEST:$REMOTE . > $OUT || true | |
184 | ### End ${profile_name} ${part_name} ### | |
185 | ''; | |
186 | in | |
187 | { | |
188 | options.services.rsyncBackup = { | |
189 | mountpoint = lib.mkOption { | |
190 | type = lib.types.path; | |
191 | description = "Path to the base folder for backups"; | |
192 | }; | |
285380fe IB |
193 | profiles = lib.mkOption { |
194 | type = lib.types.attrsOf profileModule; | |
195 | default = {}; | |
196 | description = '' | |
197 | Profiles to backup | |
198 | ''; | |
199 | }; | |
200 | ssh_key_public = lib.mkOption { | |
201 | type = lib.types.str; | |
202 | description = "Public key for the backup"; | |
203 | }; | |
204 | ssh_key_private = lib.mkOption { | |
205 | type = lib.types.str; | |
206 | description = "Private key for the backup"; | |
207 | }; | |
208 | }; | |
209 | ||
210 | config = lib.mkIf (builtins.length (builtins.attrNames cfg.profiles) > 0) { | |
285380fe IB |
211 | users.users.backup = { |
212 | isSystemUser = true; | |
213 | uid = config.ids.uids.backup; | |
214 | group = "backup"; | |
215 | extraGroups = [ "keys" ]; | |
216 | }; | |
217 | ||
218 | users.groups.backup = { | |
219 | gid = config.ids.gids.backup; | |
220 | }; | |
221 | ||
222 | services.cron.systemCronJobs = let | |
223 | backup = pkgs.writeScript "backup.sh" (builtins.concatStringsSep "\n" ([ | |
739d28ea | 224 | backup_head |
285380fe IB |
225 | ] ++ lib.mapAttrsToList backup_profile cfg.profiles)); |
226 | in [ | |
227 | '' | |
228 | 25 3,15 * * * backup ${backup} | |
229 | '' | |
230 | ]; | |
231 | ||
232 | programs.ssh.knownHosts = lib.attrsets.mapAttrs' (name: profile: lib.attrsets.nameValuePair name { | |
233 | hostNames = [ profile.host ]; | |
234 | publicKey = "${profile.host_key_type} ${profile.host_key}"; | |
235 | }) cfg.profiles; | |
236 | ||
237 | system.activationScripts.rsyncBackup = { | |
238 | deps = [ "users" ]; | |
239 | text = builtins.concatStringsSep "\n" (map (v: '' | |
240 | install -m 0700 -o backup -g backup -d ${cfg.mountpoint}/${v} ${cfg.mountpoint}/${v}/older ${cfg.mountpoint}/${v}/rsync_output | |
241 | '') (builtins.attrNames cfg.profiles) | |
242 | ); | |
243 | }; | |
244 | ||
245 | secrets.keys = [ | |
246 | { | |
247 | dest = "rsync_backup/identity"; | |
248 | user = "backup"; | |
249 | group = "backup"; | |
250 | permissions = "0400"; | |
251 | text = cfg.ssh_key_private; | |
252 | } | |
253 | { | |
254 | dest = "rsync_backup/identity.pub"; | |
255 | user = "backup"; | |
256 | group = "backup"; | |
257 | permissions = "0444"; | |
258 | text = cfg.ssh_key_public; | |
259 | } | |
260 | ]; | |
261 | }; | |
262 | } |