From a464c99fb833272f4322ea8ba7e1e50ba220f95e Mon Sep 17 00:00:00 2001 From: oddlama Date: Sun, 14 Jan 2024 18:01:35 +0100 Subject: [PATCH] feat: add restic hetzner module, script still wip --- flake.nix | 2 + hosts/sire/fs.nix | 1 + hosts/sire/guests/samba.nix | 33 ++++++++++++++++ modules/default.nix | 1 + modules/restic.nix | 57 ++++++++++++++++++++++++++++ nix/setup-hetzner-storage-boxes.nix | 12 ++++++ secrets/global.nix.age | Bin 1318 -> 1584 bytes 7 files changed, 106 insertions(+) create mode 100644 modules/restic.nix create mode 100644 nix/setup-hetzner-storage-boxes.nix diff --git a/flake.nix b/flake.nix index c381540..96cf412 100644 --- a/flake.nix +++ b/flake.nix @@ -160,6 +160,8 @@ ; } // flake-utils.lib.eachDefaultSystem (system: rec { + apps.setupHetznerStorageBoxes = import ./nix/setup-hetzner-storage-boxes.nix self; + pkgs = import nixpkgs { inherit system; config.allowUnfree = true; diff --git a/hosts/sire/fs.nix b/hosts/sire/fs.nix index bce561b..e6d0f7b 100644 --- a/hosts/sire/fs.nix +++ b/hosts/sire/fs.nix @@ -80,6 +80,7 @@ in { "rpool/local/state<" = true; "rpool/safe<" = true; "storage/safe<" = true; + "storage/bunker<" = true; }; snapshotting = { type = "periodic"; diff --git a/hosts/sire/guests/samba.nix b/hosts/sire/guests/samba.nix index 750cf03..1e9c9c7 100644 --- a/hosts/sire/guests/samba.nix +++ b/hosts/sire/guests/samba.nix @@ -209,4 +209,37 @@ in { ]; users.groups = lib.mapAttrs (_: cfg: {gid = cfg.id;}) (smbUsers // smbGroups); + + # Backups + # ======================================================================== + + age.secrets.restic-encryption-password.generator.script = "alnum"; + age.secrets.restic-ssh-privkey.generator.script = "ssh-ed25519"; + + services.restic.backups.main = { + hetznerStorageBox = let + box = config.repo.secrets.global.hetzner.storageboxes.dusk; + in { + enable = true; + inherit (box) mainUser; + inherit (box.users.samba) subUid path; + sshPrivateKeyFile = config.age.secrets.restic-ssh-privkey.path; + }; + + user = "root"; + timerConfig = { + OnCalendar = "06:15"; + RandomizedDelaySec = "3h"; + Persistent = true; + }; + initialize = true; + passwordFile = config.age.secrets.restic-encryption-password.path; + paths = ["/bunker"]; + pruneOpts = [ + "--keep-daily 14" + "--keep-weekly 7" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; } diff --git a/modules/default.nix b/modules/default.nix index 337663b..d8c96bb 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -34,6 +34,7 @@ ./oauth2-proxy.nix ./promtail.nix ./provided-domains.nix + ./restic.nix ./secrets.nix ./telegraf.nix ./wireguard-proxy.nix diff --git a/modules/restic.nix b/modules/restic.nix new file mode 100644 index 0000000..ab1697d --- /dev/null +++ b/modules/restic.nix @@ -0,0 +1,57 @@ +{lib, ...}: let + inherit + (lib) + mkEnableOption + mkIf + mkOption + types + ; +in { + options.services.restic.backups = { + type = types.attrsOf (types.submodule ({config}: { + options.hetznerStorageBox = { + enable = mkEnableOption "Automatically configure this backup to use the given hetzner storage box. Will use SFTP via SSH."; + + mainUser = mkOption { + type = types.str; + description = '' + The main user. While not technically required for restic, we still use it to + derive the subuser name and it is required for the automatic setup script + that creates the users. + ''; + }; + + subUid = mkOption { + type = types.int; + description = "The id of the subuser that was allocated on the hetzner server for this backup."; + }; + + path = mkOption { + type = types.str; + description = '' + The remote path to backup into. While not technically required for restic + (since the subuser is chrooted on the remote), we'll still use it to set + a sane repository and it is required for the automatic setup script that + creates the users. + ''; + }; + + sshPrivateKeyFile = { + type = types.path; + description = "The path to the ssh private key to use for uploading backups. Don't use a path from the nix store!"; + }; + }; + + config = let + subUser = "${config.hetznerStorageBox.mainUser}-sub${toString config.hetznerStorageBox.subUid}"; + url = "${subUser}@${subUser}.your-storagebox.de"; + in + mkIf config.hetznerStorageBox.enable { + repository = "sftp://${url}:23${config.hetznerStorageBox.path}"; + extraOptions = [ + "sftp.command='ssh -s sftp -p 23 -i ${config.hetznerStorageBox.sshPrivateKeyFile} ${url}'" + ]; + }; + })); + }; +} diff --git a/nix/setup-hetzner-storage-boxes.nix b/nix/setup-hetzner-storage-boxes.nix new file mode 100644 index 0000000..c39f4cd --- /dev/null +++ b/nix/setup-hetzner-storage-boxes.nix @@ -0,0 +1,12 @@ +self: system: let + pkgs = self.pkgs.${system}; +in { + type = "app"; + drv = pkgs.writeShellApplication { + name = "setup-hetzner-storage-boxes"; + text = '' + set -euo pipefail + + ''; + }; +} diff --git a/secrets/global.nix.age b/secrets/global.nix.age index 309acb09e5f37d84a971628a9612274fd71e96e1..44a862feaedf5feb9eab98040e681ae586603b33 100644 GIT binary patch delta 1571 zcmV+;2Hg3k3a|{2Ab(h6WKT0sYh+hbT4!!zHd!)QOKDV3R5(;uXmxfsHbyUWN>+Jo zRZ4AlYYI|naBOT%abq!1VoqZ+FhV&+Sz2ynSXfPLIA~~XHD)OmZ`ESxrSUPik0kVr@xv zL2C*vJ|Jc-XL4m>b7dfG3TZh}K?*G`Eg)rhSz=OUYIS5%F=9wqYD#WFRYpNLc6U{F zF>5(EGHrEDdVg|HbxCP?Zb1qPcA*#zc=7Qn{&~mX)Z^+8o(Eq295?1;)F_{b6M<3P z^V-R>ZB)j}&144yi3UwZOim9k#b^dMv&cjTtW%YD@9;gCJMAyaB%bz9G{PReij>6u z5KVCnafn#5tnh$mN|xe%Q`zWS$k+f(e4!Es(YzyP!GCxIB*;|7p$d?+%p3py6Z)=y&$i{lOu@PfBlS6T{ z?@fie+C_qbeP(whF8N3_d&nBsRrze@yszIXL_{ObzpEVFnwKU2z$SCX37Zpsu|IZ! z>?v2$CVvk$M`5Yz%D(c1rrO%>4QM9bcWz-!4mxE>H@WjWr2Q2K zQJFkjhw%(PeU}YcAC}g3T-e?uqz+>o`jtW+qO2~C)S{x!7uMKPI5MbBuh(ts=|8_S zZE?8_2G5PLd0jkbK%=A*GP&sG!AV~W`k0fz*niD7(RT69){<^BfoK0Kb+tC3c#Trf zipk|uD}hh}noi}K5+}S`rXL-?Q5_}Biiu$;RI-c@OC>F`bn}kHTRWQI%lrH9x#;V7 z3%q}`qb0Yt6(jDM7DpI8R9RwwZsx1d1{ZT}vo|9y55Zm-4UilrWu+O&9-*fIAQO(^ z4}ayr&Mb|~G_m=Vog)wT!|U5SFm};rrl2P@EP0X|RBn6Msgiu#DVImFm2=9jZmV;^0dX0D!Ny|Kmy6 zv1rJ7EF0ogn;Bp#!hcUqqbT&^zaq?&hB2|Xb2PkEsowE#0M#O+x`_qkBifhe-uiY0 z2Qo;NvKL8-?i=O35S8eVXUZUtyYsto04M>l{wYY#IkF3~gZ*D`Q@z4=>#^YLW%p@j*{;raJxd;Sp!*)>7 z&(Me^K(t+n5~A|GzSF(cWV8eAi>=Dn^wFP_{M%_Bgj5&rcG@f{h{_c`a z0h5ol{yNYmuGO45l;N2dGNq%^`m1&qe8W;ly4uDYT^SKepJ+3k3Om!lMTu>s4Rak+ zG;RMF=1pcqK9q8>>i`M_Bs?L1bdNguS~B=o0Z#9>Zt?kRIsLGoh8(c52i-Tc`YiMpmN_I|EXA015O8c%L#B`lh z;pkitbZm`yO(QEU+mho5#eDYh@?)SgxF@H2J27dMbbq7nA3<(=y*xpkI0mNHOmtMc zv)%}Ve?BLcu~S6{T_r#U@W3Y|KR=`3!@PD8zKTy>au5DdtQPS~Fix^F$B@&mak+b{ zdxoJxCCmk4p%b9(`azDCLAK)eYC_kGdnBb2>1o8WDi`dtcYLAv{=^lBhek>o6s2Zu z?u{AVfqy@Ohf3~99R`$l&=1iPrE#(>sOoUlmKmXO2DoWLZ99f(VU5Nh&Ey(^`AHO{ zEKHK40i12L3sH)$&E)NTRAPf1>a!C{v8e;*#E7&fTy5|Fp`BlL!-)M*<01+pt~{~nBFJ`R*5K=CJN^&qVey`qNX>Fc2Kw#@W# z-%&ng(u}9q*Fg+x#z`E<`YjV-!q4X_dVk^5td9rgRz*s+n^UW|GW*y%sAgGELbGzj z=<#J`ye)9MpkULEP)R8pxgVrKat7I3o`1A53$yh57_YzAuIEk4J;!Df!@s~(Zgz}j2WbeY`Ut+HP;|;qb_*t6$)*6RS zI!o#RK7O+Kb46F6E;~j>!OOrBpNL)|j+L1}nGulZMqZCpX)TGF?XAARwt! z%!6nnfy-^AAx?DaBb=?v>1>Z(JB=%tvH0Rf@vG$!^LHKvLE}Mq_$y_6d=Q_^IOQI4 NuE2$*{QML7Brg{xQMCX7