# Some examples for how to use this module # # Host with media files - backup /media only locally # my.modules.backups = { # enable = true; # passwordFile = config.age.secrets.restic_password.path # local.paths = [ "/media" "/home" "/var/lib/important" ]; # remote.paths = [ "/home" "/var/lib/important" ]; # Excludes /media # }; # # Another example - different exclusions for local vs remote # my.modules.backups = { # enable = true; # passwordFile = config.age.secrets.restic_password.path # local.paths = [ "/home" "/var/cache/downloads" ]; # local.exclude = [ "*.tmp" ]; # remote.paths = [ "/home" ]; # Skip cache directory for remote # remote.exclude = [ "*.tmp" "*.log" ]; # More aggressive exclusions for remote # }; { pkgs, config, lib, ... }: let cfg = config.my.modules.backups; # Helper scripts for easy backup access restic-local = pkgs.writeShellScriptBin "restic-local" '' export RESTIC_REPOSITORY="${cfg.localBasePath}/${config.networking.hostName}" export RESTIC_PASSWORD_FILE="${cfg.passwordFile}" exec ${pkgs.restic}/bin/restic "$@" ''; restic-remote = pkgs.writeShellScriptBin "restic-remote" '' export RESTIC_REPOSITORY="${cfg.remoteBaseRepository}:/${config.networking.hostName}/" export RESTIC_PASSWORD_FILE="${cfg.passwordFile}" ${lib.optionalString (cfg.remote.environmentFile != null) '' source ${cfg.remote.environmentFile} ''} exec ${pkgs.restic}/bin/restic "$@" ''; # Common backup options shared between local and remote backupOptions = { paths = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Paths to backup"; example = [ "/home" "/var/lib/important-data" ]; }; exclude = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Paths to exclude from backup"; example = [ "*.tmp" "/var/cache" ]; }; extraBackupArgs = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "--exclude-caches" "--compression=max" ]; description = "Additional arguments to pass to restic backup"; }; pruneOpts = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 3" ]; description = "Pruning options for old backups"; }; timerConfig = lib.mkOption { type = lib.types.attrs; default = { OnCalendar = "daily"; RandomizedDelaySec = "5m"; }; description = "Systemd timer configuration"; }; }; in { options.my.modules.backups = { enable = lib.mkEnableOption "backups"; passwordFile = lib.mkOption { type = lib.types.str; default = config.age.secrets.restic_password.path; description = "Path to file containing restic repository password"; example = "/run/secrets/restic-password"; }; localBasePath = lib.mkOption { type = lib.types.str; default = "/data/backups"; description = "Base path for local backup repositories"; example = "/mnt/backup-drive/backups"; }; remoteBaseRepository = lib.mkOption { type = lib.types.str; default = "gs:fcuny-infra-backups"; description = "Base repository URL for remote backups"; example = "s3:my-backup-bucket"; }; local = backupOptions; remote = backupOptions // { timerConfig = lib.mkOption { type = lib.types.attrs; default = { OnCalendar = "daily"; # No randomized delay for remote to avoid overlap with local }; description = "Systemd timer configuration for remote backups"; }; googleProjectId = lib.mkOption { type = lib.types.nullOr lib.types.str; default = "fcuny-infra"; description = "Google Cloud project ID for GCS backups"; example = "my-backup-project"; }; googleCredentialsFile = lib.mkOption { type = lib.types.nullOr lib.types.str; default = config.age.secrets.restic_gcs_credentials.path; description = "Path to Google Cloud service account credentials file"; example = "/run/secrets/gcs-credentials"; }; environmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = if cfg.remote.googleProjectId != null && cfg.remote.googleCredentialsFile != null then pkgs.writeText "restic-gcs-env" '' GOOGLE_PROJECT_ID=${cfg.remote.googleProjectId} GOOGLE_APPLICATION_CREDENTIALS=${cfg.remote.googleCredentialsFile} '' else null; description = "Environment file for remote backup authentication"; }; }; helpers = lib.mkOption { type = lib.types.bool; default = true; description = "Install helper scripts (restic-local, restic-remote)"; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ pkgs.restic ] ++ lib.optionals cfg.helpers [ restic-local restic-remote ]; services.restic.backups = lib.mkMerge [ # Local backup configuration - only if paths are specified (lib.mkIf (cfg.local.paths != [ ]) { local = { initialize = true; repository = "${cfg.localBasePath}/${config.networking.hostName}"; passwordFile = cfg.passwordFile; paths = cfg.local.paths; exclude = cfg.local.exclude; extraBackupArgs = cfg.local.extraBackupArgs; timerConfig = cfg.local.timerConfig; pruneOpts = cfg.local.pruneOpts; }; }) # Remote backup configuration - only if paths are specified (lib.mkIf (cfg.remote.paths != [ ]) { remote = { initialize = true; repository = "${cfg.remoteBaseRepository}:/${config.networking.hostName}/"; passwordFile = cfg.passwordFile; paths = cfg.remote.paths; exclude = cfg.remote.exclude; extraBackupArgs = cfg.remote.extraBackupArgs; timerConfig = cfg.remote.timerConfig; pruneOpts = cfg.remote.pruneOpts; } // lib.optionalAttrs (cfg.remote.environmentFile != null) { environmentFile = toString cfg.remote.environmentFile; }; }) ]; }; }