234 lines
6.6 KiB
Nix
234 lines
6.6 KiB
Nix
# restic.nix - Restic backend implementation
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
with lib; let
|
|
cfg = config.system.backups;
|
|
resticCfg = cfg.restic;
|
|
|
|
# Get only restic backups that are enabled
|
|
resticBackups = filterAttrs (_: backup: backup.backend == "restic" && backup.enable) cfg.backups;
|
|
|
|
# Create restic service configurations
|
|
createResticServices =
|
|
mapAttrs (
|
|
name: backup: let
|
|
# Merge global defaults with backup-specific options
|
|
serviceConfig =
|
|
recursiveUpdate resticCfg.defaultBackendOptions backup.backendOptions
|
|
// {
|
|
inherit (backup) paths;
|
|
|
|
# Use backup-specific timer or fall back to global default
|
|
timerConfig =
|
|
if backup.timerConfig != null
|
|
then backup.timerConfig
|
|
else resticCfg.timerConfig;
|
|
};
|
|
in
|
|
serviceConfig
|
|
)
|
|
resticBackups;
|
|
in {
|
|
options.system.backups.restic = {
|
|
enable = mkEnableOption "restic backup backend";
|
|
|
|
timerConfig = mkOption {
|
|
type = types.attrs;
|
|
default = {
|
|
OnCalendar = "*-*-* 05:00:00";
|
|
Persistent = true;
|
|
};
|
|
description = "Default systemd timer configuration for restic backups";
|
|
};
|
|
|
|
defaultBackendOptions = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
example = {
|
|
repository = "/backup/restic";
|
|
passwordFile = "/etc/nixos/secrets/restic-password";
|
|
initialize = true;
|
|
pruneOpts = [
|
|
"--keep-daily 7"
|
|
"--keep-weekly 5"
|
|
"--keep-monthly 12"
|
|
"--keep-yearly 75"
|
|
];
|
|
};
|
|
description = "Default backend options applied to all restic backup jobs";
|
|
};
|
|
|
|
# Advanced options
|
|
runMaintenance = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Whether to run repository maintenance after backups";
|
|
};
|
|
|
|
maintenanceTimer = mkOption {
|
|
type = types.attrs;
|
|
default = {
|
|
OnCalendar = "*-*-* 06:00:00";
|
|
Persistent = true;
|
|
};
|
|
description = "Timer configuration for maintenance tasks";
|
|
};
|
|
|
|
pruneOpts = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [
|
|
"--keep-daily 7"
|
|
"--keep-weekly 4"
|
|
"--keep-monthly 6"
|
|
"--keep-yearly 3"
|
|
];
|
|
description = "Default pruning options for maintenance";
|
|
};
|
|
};
|
|
|
|
config = mkIf resticCfg.enable {
|
|
# Register restic backend
|
|
system.backups.backends.restic = {
|
|
backupConfig,
|
|
backupName,
|
|
...
|
|
}: {
|
|
# Define the proper options schema for restic backendOptions
|
|
options = {
|
|
repository = mkOption {
|
|
type = types.str;
|
|
description = "Restic repository path or URL";
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.str;
|
|
description = "Path to file containing the repository password";
|
|
};
|
|
|
|
initialize = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Whether to initialize the repository if it doesn't exist";
|
|
};
|
|
|
|
exclude = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "Patterns to exclude from backup";
|
|
};
|
|
|
|
extraBackupArgs = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "Additional arguments passed to restic backup command";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = "User to run the backup as";
|
|
};
|
|
|
|
pruneOpts = mkOption {
|
|
type = types.listOf types.str;
|
|
default = resticCfg.pruneOpts;
|
|
description = "Pruning options for this backup";
|
|
};
|
|
};
|
|
|
|
# Default config merged with global defaults
|
|
config = {
|
|
extraBackupArgs =
|
|
[
|
|
"--tag ${backupName}"
|
|
"--verbose"
|
|
]
|
|
++ (resticCfg.defaultBackendOptions.extraBackupArgs or []);
|
|
};
|
|
};
|
|
|
|
# Create actual restic backup services
|
|
services.restic.backups = createResticServices;
|
|
|
|
# Add restic package
|
|
environment.systemPackages = [pkgs.restic];
|
|
|
|
# Systemd service customizations for restic backups
|
|
systemd.services =
|
|
(mapAttrs' (
|
|
name: backup:
|
|
nameValuePair "restic-backups-${name}" {
|
|
# Custom pre/post scripts
|
|
preStart = mkBefore backup.preBackupScript;
|
|
postStop = mkAfter backup.postBackupScript;
|
|
|
|
# Enhanced service configuration
|
|
serviceConfig = {
|
|
# Restart configuration
|
|
Restart = "on-failure";
|
|
RestartSec = "5m";
|
|
RestartMaxDelaySec = "30m";
|
|
RestartSteps = 3;
|
|
|
|
# Rate limiting
|
|
StartLimitBurst = 4;
|
|
StartLimitIntervalSec = "2h";
|
|
};
|
|
|
|
# Failure handling could be extended here for notifications
|
|
# onFailure = optional backup.notifications.failure.enable "restic-backup-${name}-failure-notify.service";
|
|
}
|
|
)
|
|
resticBackups)
|
|
// optionalAttrs resticCfg.runMaintenance {
|
|
# Repository maintenance service
|
|
restic-maintenance = {
|
|
description = "Restic repository maintenance";
|
|
after = map (name: "restic-backups-${name}.service") (attrNames resticBackups);
|
|
|
|
environment =
|
|
resticCfg.defaultBackendOptions
|
|
// {
|
|
RESTIC_CACHE_DIR = "/var/cache/restic-maintenance";
|
|
};
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = [
|
|
"${pkgs.restic}/bin/restic forget --prune ${concatStringsSep " " resticCfg.pruneOpts}"
|
|
"${pkgs.restic}/bin/restic check --read-data-subset=500M"
|
|
];
|
|
|
|
User = "root";
|
|
CacheDirectory = "restic-maintenance";
|
|
CacheDirectoryMode = "0700";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Maintenance timer
|
|
systemd.timers = mkIf resticCfg.runMaintenance {
|
|
restic-maintenance = {
|
|
description = "Timer for restic repository maintenance";
|
|
wantedBy = ["timers.target"];
|
|
timerConfig = resticCfg.maintenanceTimer;
|
|
};
|
|
};
|
|
|
|
# Helpful shell aliases
|
|
programs.zsh.shellAliases =
|
|
{
|
|
restic-snapshots = "restic snapshots --compact --group-by tags";
|
|
restic-repo-size = "restic stats --mode raw-data";
|
|
}
|
|
// (mapAttrs' (
|
|
name: _:
|
|
nameValuePair "backup-${name}" "systemctl start restic-backups-${name}"
|
|
)
|
|
resticBackups);
|
|
};
|
|
}
|