1 { lib, pkgs, config, name, ... }:
4 cfg = config.myEnv.backup;
5 varDir = "/var/lib/duply";
6 default_action = "pre_bkp_purge_purgeFull_purgeIncr";
7 duply_backup_full_with_ignored = pkgs.writeScriptBin "duply_full_with_ignored" ''
10 export DUPLY_FULL_BACKUP_WITH_IGNORED=yes
11 if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then
12 echo "duply_full_with_ignored /path/to/profile"
13 echo "Does a full backup including directories with .duplicity-ignore"
16 ${pkgs.duply}/bin/duply "$1" pre_full --force
18 duply_backup = pkgs.writeScriptBin "duply_backup" ''
19 #!${pkgs.stdenv.shell}
23 ${builtins.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList (k: v: map (remote: [
24 ''profiles+=("${remote}_${k}")''
25 ]) v.remotes) config.services.duplyBackup.profiles))}
27 if [ -f "${varDir}/last_backup_profile" ]; then
28 last_backup=$(cat ${varDir}/last_backup_profile)
29 for i in "''${!profiles[@]}"; do
30 if [[ "''${profiles[$i]}" = "$last_backup" ]]; then
35 profiles=("''${profiles[@]:$i}" "''${profiles[@]:0:$i}")
40 timeout_timestamp=$(date +%s -d "$timeout minutes")
41 for profile in "''${profiles[@]}"; do
42 if [ $(date +%s -d "now") -ge "$timeout_timestamp" ]; then
46 touch "${varDir}/$profile.log"
47 ${pkgs.duply}/bin/duply ${config.secrets.location}/backup/$profile/ ${default_action} --force >> ${varDir}/$profile.log
48 [[ $? = 0 ]] || echo -e "Error when doing backup for $profile, see above or logs in ${varDir}/$profile.log\n---------------------------------------" >&2
49 echo "$profile" > ${varDir}/last_backup_profile
53 check_backups = pkgs.writeScriptBin "duply_list_not_backuped" ''
54 #!${pkgs.stdenv.shell}
57 local dir="$1" path ignored_path
58 find "$dir" -mindepth 1 -maxdepth 1 | while IFS= read -r path; do
59 if ${pkgs.gnugrep}/bin/grep -qFx "$path" ${config.secrets.fullPaths."backup/backuped_list"}; then
61 elif ${pkgs.gnugrep}/bin/grep -q "^$path/" ${config.secrets.fullPaths."backup/backuped_list"}; then
64 while IFS= read -r ignored_path; do
65 if [[ "$path" =~ ^$ignored_path$ ]]; then
68 done < ${config.secrets.fullPaths."backup/ignored_list"}
76 duplyProfile = profile: remote: bucket: let
77 remote' = cfg.remotes.${remote};
79 if [ -z "$DUPLY_FULL_BACKUP_WITH_IGNORED" ]; then
80 GPG_PW="${cfg.password}"
82 TARGET="${remote'.remote bucket}"
83 ${lib.optionalString (remote'.remote_type == "s3") ''
84 export AWS_ACCESS_KEY_ID="${remote'.s3AccessKeyId}"
85 export AWS_SECRET_ACCESS_KEY="${remote'.s3SecretAccessKey}"
87 ${lib.optionalString (remote'.remote_type == "rsync") ''
88 DUPL_PARAMS="$DUPL_PARAMS --ssh-options=-oIdentityFile='${config.secrets.fullPaths."backup/identity"}' "
90 SOURCE="${profile.rootDir}"
91 if [ -z "$DUPLY_FULL_BACKUP_WITH_IGNORED" ]; then
92 FILENAME=".duplicity-ignore"
93 DUPL_PARAMS="$DUPL_PARAMS --exclude-if-present '$FILENAME'"
96 ARCH_DIR="${varDir}/caches"
99 # Do a full backup after 6 month
101 DUPL_PARAMS="$DUPL_PARAMS --allow-source-mismatch --full-if-older-than $MAX_FULLBKP_AGE "
102 # Backups older than 1months are deleted
106 MAX_FULLS_WITH_INCRS=1
111 services.duplyBackup.enable = lib.mkOption {
112 type = lib.types.bool;
115 Whether to enable remote backups.
118 services.duplyBackup.profiles = lib.mkOption {
119 type = lib.types.attrsOf (lib.types.submodule {
121 hash = lib.mkOption {
122 type = lib.types.bool;
125 Hash bucket and directory names
128 excludeRootDir = lib.mkOption {
129 type = lib.types.bool;
132 Exclude root dir in exclusion file
135 rootDir = lib.mkOption {
136 type = lib.types.path;
137 default = "/var/lib";
142 bucket = lib.mkOption {
143 type = lib.types.str;
148 remotes = lib.mkOption {
149 type = lib.types.listOf lib.types.str;
151 Remotes to use for backup
154 includedPaths = lib.mkOption {
155 type = lib.types.listOf lib.types.str;
158 Included paths (subdirs of rootDir)
161 excludeFile = lib.mkOption {
162 type = lib.types.lines;
165 Content to put in exclude file
168 ignoredPaths = lib.mkOption {
169 type = lib.types.listOf lib.types.str;
172 List of paths to ignore when checking non-backed-up directories
173 Can use (POSIX extended) regex
181 config = lib.mkIf config.services.duplyBackup.enable {
182 system.activationScripts.backup = ''
183 install -m 0700 -o root -g root -d ${varDir} ${varDir}/caches
185 secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (k: v:
187 bucket = if v.hash or true then builtins.hashString "sha256" v.bucket else v.bucket;
189 (lib.nameValuePair "backup/${remote}_${k}/conf" {
190 permissions = "0400";
191 text = duplyProfile v remote bucket;
193 (lib.nameValuePair "backup/${remote}_${k}/exclude" {
194 permissions = "0400";
195 text = v.excludeFile + (builtins.concatStringsSep "\n" (map (p: "+ ${v.rootDir}/${p}") v.includedPaths)) + (lib.optionalString v.excludeRootDir ''
200 (lib.nameValuePair "backup/${remote}_${k}/pre" {
205 permissions = "0500";
207 remote' = cfg.remotes.${remote};
209 #!${pkgs.stdenv.shell}
211 ${lib.optionalString (remote'.remote_type == "rsync") ''
212 # Recreate directory structure before synchronizing
213 mkdir -p ${varDir}/rsync_remotes/${remote}/${bucket}
214 ${pkgs.rsync}/bin/rsync -av -e \
215 "ssh -p ${remote'.sshRsyncPort} -oIdentityFile=${config.secrets.fullPaths."backup/identity"}" \
216 "${varDir}/rsync_remotes/${remote}/" \
217 ${remote'.sshRsyncHost}:
221 (lib.nameValuePair "backup/${remote}_${k}" {
222 permissions = "0700";
225 ]) v.remotes) config.services.duplyBackup.profiles)) // {
226 "backup/identity" = {
227 permissions = "0400";
228 text = "{{ .ssl_keys.duply_backup }}";
230 "backup/ignored_list" = {
231 permissions = "0400";
234 (v: map (p: "${v.rootDir}/${p}") v.ignoredPaths)
235 (builtins.attrValues config.services.duplyBackup.profiles);
236 in builtins.concatStringsSep "\n" (lib.flatten ignored);
238 "backup/backuped_list" = {
239 permissions = "0400";
242 (v: map (p: "${v.rootDir}/${p}") v.includedPaths)
243 (builtins.attrValues config.services.duplyBackup.profiles);
244 in builtins.concatStringsSep "\n" (lib.flatten included);
248 programs.ssh.knownHostsFiles = [
250 "duply_backup_known_hosts"
251 (builtins.concatStringsSep
257 (builtins.attrValues cfg.remotes)
263 environment.systemPackages = [ pkgs.duply check_backups duply_backup_full_with_ignored duply_backup ];
267 "0 0 * * * root ${duply_backup}/bin/duply_backup 90"