blob: 51433029feaab0e97668e218570890bda7868ebe (
plain) (
tree)
|
|
{ lib, pkgs, config, name, ... }:
let
cfg = config.myEnv.backup;
varDir = "/var/lib/duply";
default_action = "pre_bkp_purge_purgeFull_purgeIncr";
duply_backup_full_with_ignored = pkgs.writeScriptBin "duply_full_with_ignored" ''
#!${pkgs.stdenv.shell}
export DUPLY_FULL_BACKUP_WITH_IGNORED=yes
if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then
echo "duply_full_with_ignored /path/to/profile"
echo "Does a full backup including directories with .duplicity-ignore"
exit 1
fi
${pkgs.duply}/bin/duply "$1" pre_full --force
'';
duply_backup = pkgs.writeScriptBin "duply_backup" ''
#!${pkgs.stdenv.shell}
declare -a profiles
profiles=()
${builtins.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList (k: v: map (remote: [
''profiles+=("${remote}_${k}")''
]) v.remotes) config.services.duplyBackup.profiles))}
if [ -f "${varDir}/last_backup_profile" ]; then
last_backup=$(cat ${varDir}/last_backup_profile)
for i in "''${!profiles[@]}"; do
if [[ "''${profiles[$i]}" = "$last_backup" ]]; then
break
fi
done
((i+=1))
profiles=("''${profiles[@]:$i}" "''${profiles[@]:0:$i}")
fi
# timeout in minutes
timeout="''${1:-180}"
timeout_timestamp=$(date +%s -d "$timeout minutes")
for profile in "''${profiles[@]}"; do
if [ $(date +%s -d "now") -ge "$timeout_timestamp" ]; then
break
fi
touch "${varDir}/$profile.log"
${pkgs.duply}/bin/duply ${config.secrets.location}/backup/$profile/ ${default_action} --force >> ${varDir}/$profile.log
[[ $? = 0 ]] || echo -e "Error when doing backup for $profile, see above or logs in ${varDir}/$profile.log\n---------------------------------------" >&2
echo "$profile" > ${varDir}/last_backup_profile
done
'';
check_backups = pkgs.writeScriptBin "duply_list_not_backuped" ''
#!${pkgs.stdenv.shell}
do_check() {
local dir="$1" path ignored_path
find "$dir" -mindepth 1 -maxdepth 1 | while IFS= read -r path; do
if ${pkgs.gnugrep}/bin/grep -qFx "$path" ${config.secrets.fullPaths."backup/backuped_list"}; then
continue
elif ${pkgs.gnugrep}/bin/grep -q "^$path/" ${config.secrets.fullPaths."backup/backuped_list"}; then
do_check "$path"
else
while IFS= read -r ignored_path; do
if [[ "$path" =~ ^$ignored_path$ ]]; then
continue 2
fi
done < ${config.secrets.fullPaths."backup/ignored_list"}
printf '%s\n' "$path"
fi
done
}
do_check /var/lib
'';
duplyProfile = profile: remote: bucket: let
remote' = cfg.remotes.${remote};
in ''
if [ -z "$DUPLY_FULL_BACKUP_WITH_IGNORED" ]; then
GPG_PW="${cfg.password}"
fi
TARGET="${remote'.remote bucket}"
${lib.optionalString (remote'.remote_type == "s3") ''
export AWS_ACCESS_KEY_ID="${remote'.s3AccessKeyId}"
export AWS_SECRET_ACCESS_KEY="${remote'.s3SecretAccessKey}"
''}
${lib.optionalString (remote'.remote_type == "rsync") ''
DUPL_PARAMS="$DUPL_PARAMS --ssh-options=-oIdentityFile='${config.secrets.fullPaths."backup/identity"}' "
''}
SOURCE="${profile.rootDir}"
if [ -z "$DUPLY_FULL_BACKUP_WITH_IGNORED" ]; then
FILENAME=".duplicity-ignore"
DUPL_PARAMS="$DUPL_PARAMS --exclude-if-present '$FILENAME'"
fi
VERBOSITY=4
ARCH_DIR="${varDir}/caches"
DUPL_PYTHON_BIN=""
# Do a full backup after 6 month
MAX_FULLBKP_AGE=6M
DUPL_PARAMS="$DUPL_PARAMS --allow-source-mismatch --full-if-older-than $MAX_FULLBKP_AGE "
# Backups older than 1months are deleted
MAX_AGE=1M
# Keep 1 full backup
MAX_FULL_BACKUPS=1
MAX_FULLS_WITH_INCRS=1
'';
in
{
options = {
services.duplyBackup.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable remote backups.
'';
};
services.duplyBackup.profiles = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
hash = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Hash bucket and directory names
'';
};
excludeRootDir = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Exclude root dir in exclusion file
'';
};
rootDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib";
description = ''
Path to backup
'';
};
bucket = lib.mkOption {
type = lib.types.str;
description = ''
Bucket to use
'';
};
remotes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Remotes to use for backup
'';
};
includedPaths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = ''
Included paths (subdirs of rootDir)
'';
};
excludeFile = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
Content to put in exclude file
'';
};
ignoredPaths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = ''
List of paths to ignore when checking non-backed-up directories
Can use (POSIX extended) regex
'';
};
};
});
};
};
config = lib.mkIf config.services.duplyBackup.enable {
system.activationScripts.backup = ''
install -m 0700 -o root -g root -d ${varDir} ${varDir}/caches
'';
secrets.keys = lib.listToAttrs (lib.flatten (lib.mapAttrsToList (k: v:
let
bucket = if v.hash or true then builtins.hashString "sha256" v.bucket else v.bucket;
in map (remote: [
(lib.nameValuePair "backup/${remote}_${k}/conf" {
permissions = "0400";
text = duplyProfile v remote bucket;
})
(lib.nameValuePair "backup/${remote}_${k}/exclude" {
permissions = "0400";
text = v.excludeFile + (builtins.concatStringsSep "\n" (map (p: "+ ${v.rootDir}/${p}") v.includedPaths)) + (lib.optionalString v.excludeRootDir ''
- **
'');
})
(lib.nameValuePair "backup/${remote}_${k}/pre" {
keyDependencies = [
pkgs.bash
pkgs.rsync
];
permissions = "0500";
text = let
remote' = cfg.remotes.${remote};
in ''
#!${pkgs.stdenv.shell}
${lib.optionalString (remote'.remote_type == "rsync") ''
# Recreate directory structure before synchronizing
mkdir -p ${varDir}/rsync_remotes/${remote}/${bucket}
${pkgs.rsync}/bin/rsync -av -e \
"ssh -p ${remote'.sshRsyncPort} -oIdentityFile=${config.secrets.fullPaths."backup/identity"}" \
"${varDir}/rsync_remotes/${remote}/" \
${remote'.sshRsyncHost}:
''}
'';
})
(lib.nameValuePair "backup/${remote}_${k}" {
permissions = "0700";
isDir = true;
})
]) v.remotes) config.services.duplyBackup.profiles)) // {
"backup/identity" = {
permissions = "0400";
text = "{{ .ssl_keys.duply_backup }}";
};
"backup/ignored_list" = {
permissions = "0400";
text = let
ignored = map
(v: map (p: "${v.rootDir}/${p}") v.ignoredPaths)
(builtins.attrValues config.services.duplyBackup.profiles);
in builtins.concatStringsSep "\n" (lib.flatten ignored);
};
"backup/backuped_list" = {
permissions = "0400";
text = let
included = map
(v: map (p: "${v.rootDir}/${p}") v.includedPaths)
(builtins.attrValues config.services.duplyBackup.profiles);
in builtins.concatStringsSep "\n" (lib.flatten included);
};
};
programs.ssh.knownHostsFiles = [
(pkgs.writeText
"duply_backup_known_hosts"
(builtins.concatStringsSep
"\n"
(builtins.filter
(v: v != null)
(builtins.map
(v: v.sshKnownHosts)
(builtins.attrValues cfg.remotes)
)
)
)
)
];
environment.systemPackages = [ pkgs.duply check_backups duply_backup_full_with_ignored duply_backup ];
services.cron = {
enable = true;
systemCronJobs = [
"0 0 * * * root ${duply_backup}/bin/duply_backup 90"
];
};
};
}
|