diff --git a/flake.nix b/flake.nix index 715d356..a432fb9 100644 --- a/flake.nix +++ b/flake.nix @@ -62,6 +62,7 @@ ] ++ (with pkgs; [ age + age-plugin-yubikey opentofu nomad gh diff --git a/hosts/ares.nix b/hosts/ares.nix index 07a5061..fb88423 100644 --- a/hosts/ares.nix +++ b/hosts/ares.nix @@ -1,4 +1,10 @@ -{ lib, profiles, ... }: +{ + lib, + config, + profiles, + secretsDir, + ... +}: let mattermost-dirs = [ "config" @@ -30,6 +36,24 @@ in x: "d /var/lib/mattermost/${x} 0750 2000 2000" ); + dsekt.restic = { + backupPrepareCommand = '' + sudo -u postgres pg_dumpall > /root/postgres_dump.sql + ''; + + paths = [ + "/root/postgres_dump.sql" + "/var/lib/mattermost" + ]; + + targets.s3 = { + repository = "s3:https://s3.amazonaws.com/dsekt-restic-ares"; + credentialsEnvFile = config.age.secrets.restic-s3-creds-ares.path; + }; + }; + + age.secrets.restic-s3-creds-ares.file = secretsDir + "/restic-s3-creds-ares.env.age"; + # Change this if you want to lose all data on this machine! system.stateVersion = "23.11"; } diff --git a/modules/restic.nix b/modules/restic.nix new file mode 100644 index 0000000..367893a --- /dev/null +++ b/modules/restic.nix @@ -0,0 +1,115 @@ +{ + config, + lib, + secretsDir, + ... +}: +{ + options.dsekt.restic = { + paths = lib.mkOption { + type = with lib.types; listOf singleLineStr; + default = [ ]; + example = [ + "/home" + "/var/lib/service" + ]; + description = lib.mdDoc '' + Paths to back up. If empty, no backups are performed. + ''; + }; + + backupPrepareCommand = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = lib.mdDoc '' + Script to run before starting the backup process. + ''; + }; + + targets = lib.mkOption { + default = { }; + description = lib.mdDoc '' + Restic repositories to back up to. If empty, no backups are performed. + ''; + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + repository = lib.mkOption { + type = lib.types.singleLineStr; + example = "s3:https://s3.amazonaws.com/bucket-name"; + description = lib.mdDoc '' + Restic repository to back up to. + ''; + }; + + credentialsEnvFile = lib.mkOption { + type = lib.types.singleLineStr; + example = "/etc/restic/creds"; + description = lib.mdDoc '' + Credentials to authenticate with. Must be in env-file format, + understandable by systemd. + + Example for S3 (`s3:https://s3.amazonaws.com/bucket-name`): + ``` + AWS_DEFAULT_REGION=eu-north-1 + AWS_ACCESS_KEY_ID=something + AWS_SECRET_ACCESS_KEY=something-else + RESTIC_PASSWORD=secret + ``` + + Example for restic REST server (`rest:https://example.com/path`): + ``` + RESTIC_PASSWORD=secret + RESTIC_REST_USERNAME=client-hostname + RESTIC_REST_PASSWORD=password123 + ``` + (If server is configured to use private repositories, + `RESTIC_REST_USERNAME` must match the path provided.) + ''; + }; + + timer = lib.mkOption { + type = lib.types.singleLineStr; + default = "03:42"; + example = "daily UTC"; # midnight + description = lib.mdDoc '' + When to run the backup. See {manpage}`systemd.timer(7)` ยง Calendar Events. + ''; + }; + }; + } + ) + ); + }; + }; + + config = + let + cfg = config.dsekt.restic; + in + lib.mkIf (builtins.length cfg.paths != 0) { + + services.restic.backups = builtins.mapAttrs (name: target: { + inherit (cfg) paths backupPrepareCommand; + inherit (target) repository; + + initialize = true; + timerConfig = { + OnCalendar = target.timer; + RandomizedDelaySec = 600; # 10min + Persistent = true; + }; + + passwordFile = "/no-repo-password-set"; # environmentFile (hopefully) overrides this + environmentFile = target.credentialsEnvFile; + pruneOpts = [ + "--keep-within-daily 7d" + "--keep-within-weekly 5w" + "--keep-within-monthly 12m" + "--keep-within-yearly 10y" + ]; + }) cfg.targets; + }; +} diff --git a/profiles/users.nix b/profiles/users.nix index dba211d..19caf09 100644 --- a/profiles/users.nix +++ b/profiles/users.nix @@ -37,5 +37,5 @@ ]; shell = pkgs.bash; }; - users.groups.deploy = {}; + users.groups.deploy = { }; } diff --git a/secrets/restic-s3-creds-ares.env.age b/secrets/restic-s3-creds-ares.env.age new file mode 100644 index 0000000..72f1a05 Binary files /dev/null and b/secrets/restic-s3-creds-ares.env.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 1c6dccb..7a0ff9e 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -33,4 +33,7 @@ in # `CLOUDFLARE_DNS_API_TOKEN=...` "cloudflare-dns-api-token.env.age".publicKeys = sysadmins ++ [ ares ]; + + # `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `RESTIC_PASSWORD` + "restic-s3-creds-ares.env.age".publicKeys = sysadmins ++ [ ares ]; }