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