aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--machines/nixos/x86_64-linux/vm-synology.nix9
-rw-r--r--modules/backups.nix212
-rw-r--r--modules/default.nix1
-rw-r--r--profiles/git-server.nix15
4 files changed, 237 insertions, 0 deletions
diff --git a/machines/nixos/x86_64-linux/vm-synology.nix b/machines/nixos/x86_64-linux/vm-synology.nix
index 0dfbc14..4b499f2 100644
--- a/machines/nixos/x86_64-linux/vm-synology.nix
+++ b/machines/nixos/x86_64-linux/vm-synology.nix
@@ -71,5 +71,14 @@
};
};
+ my.modules.backups = {
+ enable = true;
+ passwordFile = config.age.secrets.restic_password.path;
+ remote = {
+ googleProjectId = "fcuny-infra";
+ googleCredentialsFile = config.age.secrets.restic_gcs_credentials.path;
+ };
+ };
+
system.stateVersion = "23.11"; # Did you read the comment?
}
diff --git a/modules/backups.nix b/modules/backups.nix
new file mode 100644
index 0000000..0724053
--- /dev/null
+++ b/modules/backups.nix
@@ -0,0 +1,212 @@
+# 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;
+ };
+ })
+ ];
+ };
+}
diff --git a/modules/default.nix b/modules/default.nix
index 441a9b8..b8e8d0b 100644
--- a/modules/default.nix
+++ b/modules/default.nix
@@ -4,5 +4,6 @@
./home.nix
./host-config.nix
./nas-client.nix
+ ./backups.nix
];
}
diff --git a/profiles/git-server.nix b/profiles/git-server.nix
index 27eebc7..6f523a8 100644
--- a/profiles/git-server.nix
+++ b/profiles/git-server.nix
@@ -22,4 +22,19 @@
defaultBranch = main
''}"
];
+
+ my.modules.backups = {
+ local.paths = [ "/var/lib/gitolite" ];
+ local.exclude = [
+ "/var/lib/gitolite/.bash_history"
+ "/var/lib/gitolite/.ssh"
+ "/var/lib/gitolite/.viminfo"
+ ];
+ remote.paths = [ "/var/lib/gitolite" ];
+ remote.exclude = [
+ "/var/lib/gitolite/.bash_history"
+ "/var/lib/gitolite/.ssh"
+ "/var/lib/gitolite/.viminfo"
+ ];
+ };
}