From 24fc202b29e7a36116b5487c7209e90d8454359e Mon Sep 17 00:00:00 2001 From: Rafael Oliveira Date: Fri, 27 Sep 2024 11:52:57 +0200 Subject: [PATCH] backup postgres and mattermost to s3 Closes #4 --- flake.nix | 1 + hosts/ares.nix | 26 +++++- modules/restic.nix | 115 +++++++++++++++++++++++++++ profiles/users.nix | 2 +- secrets/restic-s3-creds-ares.env.age | Bin 0 -> 876 bytes secrets/secrets.nix | 3 + 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 modules/restic.nix create mode 100644 secrets/restic-s3-creds-ares.env.age 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 0000000000000000000000000000000000000000..72f1a05c002883d0168d482b3ebd861babf9119a GIT binary patch literal 876 zcmZ9|`-|HI0KjoMd|=hhAI8SUJcGh~)ULg}1LGiVdQGlr(zH#|luc+Jm!z+xc{G=D zCx;xvZFRs;uLRSy$pzpy=1bF|IecI zVFZ%^iF;+HUDeYWs}(XiM86$YD^h4qNTlEB1(=1jq;@VW1vHglND(R$n&1Y-G}Fma zu`EGmBP~yP>7--m-JDDdSy{tcP3J$$_pOwng9tJ~0Br0w1rw;wWT6Jwq0}G_BTYdw zvtd0hQ&pXldn#V1-DaM3+8)~m{X`$h@tJy@j0!aj8#1hcgz2f!$|72+PIrpQ494MA zvDQHP?K?GFLxMpB%-Z0v=E4;LE|(h$-fDSaj%D<4pn8yO^ea)`10f#PLQqL$W;~e6 zg?(LWsvu85rbIe*x_))x#F~;faeeQO!M(4opSbnK z!H2J$yfTjp+|9!CrQ=_n-I;_Qn%q{uc=6QuO%J~szGx~NkDWfgW%Gegg5S1XT8)1{ zu=)7scW<2A^X}L0tdEXd_-nN1=RZojUcGjf8a;VoWh}XgEgZ}d6C&